2 using System.Collections.Generic;
3 using System.Diagnostics;
13 using Microsoft.Xna.Framework;
14 using Microsoft.Xna.Framework.Graphics;
15 using Microsoft.Xna.Framework.Input;
16 using System.Collections.Immutable;
53 public enum OutlinePosition
60 public static GUICanvas Canvas => GUICanvas.Instance;
63 public static readonly SamplerState SamplerState =
new SamplerState()
65 Filter = TextureFilter.Linear,
66 AddressU = TextureAddressMode.Wrap,
67 AddressV = TextureAddressMode.Wrap,
68 AddressW = TextureAddressMode.Wrap,
69 BorderColor = Color.White,
72 MipMapLevelOfDetailBias = -0.8f,
73 ComparisonFunction = CompareFunction.Never,
74 FilterMode = TextureFilterMode.Default,
77 public static readonly SamplerState SamplerStateClamp =
new SamplerState()
79 Filter = TextureFilter.Linear,
80 AddressU = TextureAddressMode.Clamp,
81 AddressV = TextureAddressMode.Clamp,
82 AddressW = TextureAddressMode.Clamp,
83 BorderColor = Color.White,
86 MipMapLevelOfDetailBias = -0.8f,
87 ComparisonFunction = CompareFunction.Never,
88 FilterMode = TextureFilterMode.Default,
91 public static readonly
string[] VectorComponentLabels = {
"X",
"Y",
"Z",
"W" };
92 public static readonly
string[] RectComponentLabels = {
"X",
"Y",
"W",
"H" };
93 public static readonly
string[] ColorComponentLabels = {
"R",
"G",
"B",
"A" };
95 private static readonly
object mutex =
new object();
97 public static readonly Vector2 ReferenceResolution =
new Vector2(1920f, 1080f);
98 public static float Scale => (UIWidth / ReferenceResolution.X + GameMain.GraphicsHeight / ReferenceResolution.Y) / 2.0f * GameSettings.CurrentConfig.Graphics.HUDScale;
99 public static float xScale => UIWidth / ReferenceResolution.X * GameSettings.CurrentConfig.Graphics.HUDScale;
100 public static float yScale => GameMain.GraphicsHeight / ReferenceResolution.Y * GameSettings.CurrentConfig.Graphics.HUDScale;
101 public static int IntScale(
float f) => (int)(f * Scale);
102 public static int IntScaleFloor(
float f) => (int)Math.Floor(f * Scale);
103 public static int IntScaleCeiling(
float f) => (int)Math.Ceiling(f * Scale);
104 public static float AdjustForTextScale(
float f) => f * GameSettings.CurrentConfig.Graphics.TextScale;
105 public static float HorizontalAspectRatio => GameMain.GraphicsWidth / (float)GameMain.GraphicsHeight;
106 public static float VerticalAspectRatio => GameMain.GraphicsHeight / (
float)GameMain.GraphicsWidth;
107 public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
108 public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X);
112 public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f;
114 public static bool IsUltrawide => HorizontalAspectRatio > 2.3f;
116 public static int UIWidth
122 return (
int)(GameMain.GraphicsHeight * ReferenceResolution.X / ReferenceResolution.Y);
126 return GameMain.GraphicsWidth;
131 public static float SlicedSpriteScale
135 if (Math.Abs(1.0f - Scale) < 0.1f)
144 private static Texture2D solidWhiteTexture;
145 public static Texture2D WhiteTexture => solidWhiteTexture;
146 private static GUICursor MouseCursorSprites => GUIStyle.CursorSprite;
148 private static bool debugDrawSounds, debugDrawEvents;
150 private static DebugDrawMetaData debugDrawMetaData;
159 public static GraphicsDevice GraphicsDevice =>
GameMain.
Instance.GraphicsDevice;
161 private static readonly List<GUIMessage> messages =
new List<GUIMessage>();
163 public static GUIFrame PauseMenu {
get;
private set; }
164 public static GUIFrame SettingsMenuContainer {
get;
private set; }
165 public static Sprite Arrow => GUIStyle.Arrow.Value.Sprite;
167 public static bool HideCursor;
174 public static bool ScreenChanged;
176 private static bool settingsMenuOpen;
177 public static bool SettingsMenuOpen
179 get {
return settingsMenuOpen; }
182 if (value == SettingsMenuOpen) {
return; }
186 SettingsMenuContainer =
new GUIFrame(
new RectTransform(Vector2.One, Canvas,
Anchor.Center), style:
null);
187 new GUIFrame(
new RectTransform(GUI.Canvas.RelativeSize, SettingsMenuContainer.RectTransform,
Anchor.Center), style:
"GUIBackgroundBlocker");
189 var settingsMenuInner =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.8f), SettingsMenuContainer.RectTransform,
Anchor.Center, scaleBasis:
ScaleBasis.Smallest) { MinSize = new Point(640, 480) });
190 SettingsMenu.Create(settingsMenuInner.RectTransform);
194 SettingsMenu.Instance?.Close();
196 settingsMenuOpen = value;
200 public static bool PauseMenuOpen {
get;
private set; }
202 public static bool InputBlockingMenuOpen =>
207 || GameSession.IsTabMenuOpen
208 || GameMain.GameSession?.GameMode is { Paused:
true }
210 || GameMain.GameSession?.Campaign is { SlideshowPlayer: {
Finished:
false, Visible:
true } };
212 public static bool PreventPauseMenuToggle =
false;
214 public static Color ScreenOverlayColor
220 public static bool DisableHUD, DisableUpperHUD, DisableItemHighlights, DisableCharacterNames;
222 private static bool isSavingIndicatorEnabled;
223 private static Color savingIndicatorColor = Color.Transparent;
224 private static bool IsSavingIndicatorVisible => savingIndicatorColor.A > 0;
225 private static float savingIndicatorSpriteIndex;
226 private static float savingIndicatorColorLerpAmount;
227 private static SavingIndicatorState savingIndicatorState = SavingIndicatorState.None;
228 private static float? timeUntilSavingIndicatorDisabled;
230 private static string loadedSpritesText;
231 private static DateTime loadedSpritesUpdateTime;
233 private enum SavingIndicatorState
240 public static void Init()
243 CrossThread.RequestExecutionOnMainThread(() =>
245 solidWhiteTexture =
new Texture2D(GraphicsDevice, 1, 1);
246 solidWhiteTexture.SetData(
new Color[] { Color.White });
253 public static void Draw(Camera cam, SpriteBatch spriteBatch)
257 usedIndicatorAngles.Clear();
262 updateListSet.Clear();
263 Screen.Selected?.AddToGUIUpdateList();
264 ScreenChanged =
false;
267 foreach (GUIComponent c
in updateList)
269 c.DrawAuto(spriteBatch);
273 foreach (GUIComponent c
in updateList)
275 if (c is not GUITextBox box) {
continue; }
276 box.DrawIMEPreview(spriteBatch);
279 if (ScreenOverlayColor.A > 0.0f)
283 new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
284 ScreenOverlayColor,
true);
288 string line1 =
"Barotrauma Unstable v" + GameMain.Version;
289 string line2 =
"(" + AssemblyInfo.BuildString +
", branch " + AssemblyInfo.GitBranch +
", revision " + AssemblyInfo.GitRevision +
")";
291 Rectangle watermarkRect =
new Rectangle(-50, GameMain.GraphicsHeight - 80, 50 + (
int)(Math.Max(GUIStyle.LargeFont.MeasureString(line1).X, GUIStyle.Font.MeasureString(line2).X) * 1.2f), 100);
296 if (Screen.Selected == GameMain.GameScreen)
298 yOffset = (int)(-HUDLayoutSettings.ChatBoxArea.Height * 1.2f);
299 watermarkRect.Y += yOffset;
302 if (Screen.Selected == GameMain.GameScreen || Screen.Selected == GameMain.SubEditorScreen)
307 GUIStyle.GetComponentStyle(
"OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw(
308 spriteBatch, watermarkRect, Color.Black * 0.8f * alpha);
309 GUIStyle.LargeFont.DrawString(spriteBatch, line1,
310 new Vector2(10, GameMain.GraphicsHeight - 30 - GUIStyle.LargeFont.MeasureString(line1).Y + yOffset), Color.White * 0.6f * alpha);
311 GUIStyle.Font.DrawString(spriteBatch, line2,
312 new Vector2(10, GameMain.GraphicsHeight - 30 + yOffset), Color.White * 0.6f * alpha);
314 if (Screen.Selected != GameMain.GameScreen)
317 new Rectangle(20 + (
int)Math.Max(GUIStyle.LargeFont.MeasureString(line1).X, GUIStyle.Font.MeasureString(line2).X), GameMain.GraphicsHeight - (
int)(45 * Scale) + yOffset, (
int)(150 * Scale), (
int)(40 * Scale));
318 if (DrawButton(spriteBatch, buttonRect,
"Report Bug", GUIStyle.GetComponentStyle(
"GUIBugButton").Color * 0.8f))
320 GameMain.Instance.ShowBugReporter();
327 DrawSavingIndicator(spriteBatch);
331 float startY = 10.0f;
332 float yStep = AdjustForTextScale(18) * yScale;
333 if (GameMain.ShowFPS || GameMain.DebugDraw || GameMain.ShowPerf)
336 DrawString(spriteBatch,
new Vector2(10, y),
337 "FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
338 Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
339 if (GameMain.GameSession !=
null && GameMain.GameSession.RoundDuration > 1.0)
342 DrawString(spriteBatch,
new Vector2(10, y),
343 $
"Physics: {GameMain.CurrentUpdateRate}",
344 (GameMain.CurrentUpdateRate < Timing.FixedUpdateRate) ? Color.Red : Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
346 if (GameMain.DebugDraw || GameMain.ShowPerf)
349 DrawString(spriteBatch,
new Vector2(10, y),
350 "Active lights: " + Lights.LightManager.ActiveLightCount,
351 Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
353 DrawString(spriteBatch,
new Vector2(10, y),
354 "Physics: " + GameMain.World.UpdateTime.TotalMilliseconds +
" ms",
355 Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
359 DrawString(spriteBatch,
new Vector2(10, y),
360 $
"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.Count(b => b != null && b.Awake && b.Enabled)} awake, {GameMain.World.BodyList.Count(b => b != null && b.Awake && b.BodyType == BodyType.Dynamic && b.Enabled)} dynamic)",
361 Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
363 catch (InvalidOperationException)
365 DebugConsole.AddWarning(
"Exception while rendering debug info. Physics bodies may have been created or removed while rendering.");
368 DrawString(spriteBatch,
new Vector2(10, y),
369 "Particle count: " + GameMain.ParticleManager.ParticleCount +
"/" + GameMain.ParticleManager.MaxParticles,
370 Color.Lerp(GUIStyle.Green, GUIStyle.Red, (GameMain.ParticleManager.ParticleCount / (
float)GameMain.ParticleManager.MaxParticles)), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
375 if (GameMain.ShowPerf)
379 DrawString(spriteBatch,
new Vector2(x, y),
380 "Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString(
"0.00") +
" ms" +
381 " Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString(
"0.00") +
" ms",
382 GUIStyle.Green, Color.Black * 0.8f, font: GUIStyle.SmallFont);
384 GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch,
new Rectangle((
int)x, (
int)y, 170, 50), color: GUIStyle.Green);
387 DrawString(spriteBatch,
new Vector2(x, y),
388 "Update - Avg: " + GameMain.PerformanceCounter.UpdateTimeGraph.Average().ToString(
"0.00") +
" ms" +
389 " Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString(
"0.00") +
" ms",
390 Color.LightBlue, Color.Black * 0.8f, font: GUIStyle.SmallFont);
392 GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch,
new Rectangle((
int)x, (
int)y, 170, 50), color: Color.LightBlue);
394 foreach (
string key
in GameMain.PerformanceCounter.GetSavedIdentifiers.OrderBy(i => i))
396 float elapsedMillisecs = GameMain.PerformanceCounter.GetAverageElapsedMillisecs(key);
398 int categoryDepth = key.Count(c => c ==
':');
400 float runningSlowThreshold = 10.0f / categoryDepth;
401 DrawString(spriteBatch,
new Vector2(x + categoryDepth * 15, y),
402 key.Split(
':').Last() +
": " + elapsedMillisecs.ToString(
"0.00"),
403 ToolBox.GradientLerp(elapsedMillisecs / runningSlowThreshold, Color.LightGreen, GUIStyle.Yellow, GUIStyle.Orange, GUIStyle.Red, Color.Magenta), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
408 DrawString(spriteBatch,
new Vector2(x, y),
"Grids: " +
Powered.
Grids.Count, Color.LightGreen, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
411 if (Settings.EnableDiagnostics)
414 DrawString(spriteBatch,
new Vector2(x, y),
"ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds.ToString(
"0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (
float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
415 DrawString(spriteBatch,
new Vector2(x, y + yStep),
"ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds.ToString(
"0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (
float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
416 DrawString(spriteBatch,
new Vector2(x, y + yStep * 2),
"AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds.ToString(
"0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (
float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
417 DrawString(spriteBatch,
new Vector2(x, y + yStep * 3),
"NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds.ToString(
"0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (
float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
418 DrawString(spriteBatch,
new Vector2(x, y + yStep * 4),
"ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds.ToString(
"0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (
float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
419 DrawString(spriteBatch,
new Vector2(x, y + yStep * 5),
"SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds.ToString(
"0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (
float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
423 if (GameMain.DebugDraw && !
Submarine.Unloading && !(Screen.Selected is RoundSummaryScreen))
425 float y = startY + yStep * 6;
427 if (Screen.Selected.Cam !=
null)
430 DrawString(spriteBatch,
new Vector2(10, y),
431 "Camera pos: " + Screen.Selected.Cam.Position.ToPoint() +
", zoom: " + Screen.Selected.Cam.Zoom,
432 Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
438 DrawString(spriteBatch,
new Vector2(10, y),
439 "Sub pos: " +
Submarine.MainSub.WorldPosition.ToPoint(),
440 Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
443 if (loadedSpritesText ==
null || DateTime.Now > loadedSpritesUpdateTime)
445 loadedSpritesText =
"Loaded sprites: " + Sprite.LoadedSprites.Count() +
"\n(" + Sprite.LoadedSprites.Select(s => s.FilePath).Distinct().Count() +
" unique textures)";
446 loadedSpritesUpdateTime = DateTime.Now +
new TimeSpan(0, 0, seconds: 5);
449 DrawString(spriteBatch,
new Vector2(10, y), loadedSpritesText, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
453 float soundTextY = 0;
454 DrawString(spriteBatch,
new Vector2(500, soundTextY),
455 "Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
458 DrawString(spriteBatch,
new Vector2(500, soundTextY),
459 "Current playback amplitude: " + GameMain.SoundManager.PlaybackAmplitude.ToString(), Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
463 DrawString(spriteBatch,
new Vector2(500, soundTextY),
464 "Compressed dynamic range gain: " + GameMain.SoundManager.CompressionDynamicRangeGain.ToString(), Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
468 DrawString(spriteBatch,
new Vector2(500, soundTextY),
469 "Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount +
" (" + GameMain.SoundManager.UniqueLoadedSoundCount +
" unique)", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
474 Color clr = Color.White;
475 string soundStr = i +
": ";
477 if (playingSoundChannel ==
null)
484 soundStr += Path.GetFileNameWithoutExtension(playingSoundChannel.
Sound.
Filename);
487 if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.G))
489 if (PlayerInput.MousePosition.Y >= soundTextY && PlayerInput.MousePosition.Y <= soundTextY + 12)
491 GameMain.SoundManager.DebugSource(i);
496 if (playingSoundChannel.
Looping)
498 soundStr +=
" (looping)";
503 soundStr +=
" (streaming)";
508 soundStr +=
" (stopped)";
513 if (playingSoundChannel.
Muffled)
515 soundStr +=
" (muffled)";
516 clr = Color.Lerp(clr, Color.LightGray, 0.5f);
520 soundStr +=
". Fading out...";
521 clr = Color.Lerp(clr, Color.Black, 0.15f);
526 DrawString(spriteBatch,
new Vector2(500, soundTextY), soundStr, clr, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
532 DrawString(spriteBatch,
new Vector2(500, 0),
533 "Ctrl+S to show sound debug info", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
540 DrawString(spriteBatch,
new Vector2(10, y),
541 "Ctrl+E to hide EventManager debug info", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
542 GameMain.GameSession?.EventManager?.DebugDrawHUD(spriteBatch, y + 15 * yScale);
546 DrawString(spriteBatch,
new Vector2(10, y),
547 "Ctrl+E to show EventManager debug info", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
550 if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
553 if (debugDrawMetaData.Enabled)
555 string text =
"Ctrl+M to hide campaign metadata debug info\n\n" +
556 $
"Ctrl+1 to {(debugDrawMetaData.FactionMetadata ? "hide
" : "show
")} faction reputations, \n" +
557 $
"Ctrl+2 to {(debugDrawMetaData.UpgradeLevels ? "hide
" : "show
")} upgrade levels, \n" +
558 $
"Ctrl+3 to {(debugDrawMetaData.UpgradePrices ? "hide
" : "show
")} upgrade prices";
559 Vector2 textSize = GUIStyle.SmallFont.MeasureString(text);
560 Vector2 pos =
new Vector2(GameMain.GraphicsWidth - (textSize.X + 10), 300);
561 DrawString(spriteBatch, pos, text, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
562 pos.Y += textSize.Y + 8;
563 campaignMode.CampaignMetadata?.DebugDraw(spriteBatch, pos, campaignMode, debugDrawMetaData);
567 const string text =
"Ctrl+M to show campaign metadata debug info";
568 DrawString(spriteBatch,
new Vector2(GameMain.GraphicsWidth - (GUIStyle.SmallFont.MeasureString(text).X + 10), 300),
569 text, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
573 IEnumerable<string> strings;
576 RectTransform mouseOnRect = MouseOn.RectTransform;
577 bool isAbsoluteOffsetInUse = mouseOnRect.AbsoluteOffset != Point.Zero || mouseOnRect.RelativeOffset == Vector2.Zero;
579 strings =
new string[]
581 $
"Selected UI Element: {MouseOn.GetType().Name} ({ MouseOn.Style?.Element.Name.LocalName ?? "no style
" }, {MouseOn.Rect}",
582 $
"Relative Offset: {mouseOnRect.RelativeOffset} | Absolute Offset: {(isAbsoluteOffsetInUse ? mouseOnRect.AbsoluteOffset : mouseOnRect.ParentRect.MultiplySize(mouseOnRect.RelativeOffset))}{(isAbsoluteOffsetInUse ? "" : " (Calculated from RelativeOffset)
")}",
583 $
"Anchor: {mouseOnRect.Anchor} | Pivot: {mouseOnRect.Pivot}"
588 strings =
new string[]
590 $
"GUI.Scale: {Scale}",
591 $
"GUI.xScale: {xScale}",
592 $
"GUI.yScale: {yScale}",
593 $
"RelativeHorizontalAspectRatio: {RelativeHorizontalAspectRatio}",
594 $
"RelativeVerticalAspectRatio: {RelativeVerticalAspectRatio}",
598 strings = strings.Concat(
new string[] { $
"Cam.Zoom: {Screen.Selected.Cam?.Zoom ?? 0f}" });
600 int padding = IntScale(10);
603 foreach (
string str
in strings)
605 Vector2 stringSize = GUIStyle.SmallFont.MeasureString(str);
607 DrawString(spriteBatch,
new Vector2(GameMain.GraphicsWidth - (
int)stringSize.X - padding, yPos), str, Color.LightGreen, Color.Black, 0, GUIStyle.SmallFont);
608 yPos += (int)stringSize.Y + padding / 2;
612 GameMain.GameSession?.EventManager?.DrawPinnedEvent(spriteBatch);
614 if (HUDLayoutSettings.DebugDraw) { HUDLayoutSettings.Draw(spriteBatch); }
616 GameMain.Client?.Draw(spriteBatch);
618 if (
Character.Controlled?.Inventory !=
null)
622 Inventory.DrawFront(spriteBatch);
626 DrawMessages(spriteBatch, cam);
628 if (MouseOn !=
null && !MouseOn.ToolTip.IsNullOrWhiteSpace())
630 MouseOn.DrawToolTip(spriteBatch);
633 if (SubEditorScreen.IsSubEditor())
636 switch (SubEditorScreen.DraggedItemPrefab)
638 case ItemPrefab itemPrefab:
640 var sprite = itemPrefab.InventoryIcon ?? itemPrefab.Sprite;
641 sprite?.Draw(spriteBatch, PlayerInput.MousePosition, scale: Math.Min(64 / sprite.size.X, 64 / sprite.size.Y) * Scale);
644 case ItemAssemblyPrefab itemAssemblyPrefab:
646 itemAssemblyPrefab.Draw(spriteBatch, PlayerInput.MousePosition.FlipY());
652 DrawSavingIndicator(spriteBatch);
653 DrawCursor(spriteBatch);
658 public static void DrawMessageBoxesOnly(SpriteBatch spriteBatch)
660 bool anyDrawn =
false;
661 foreach (var component
in updateList)
663 component.DrawAuto(spriteBatch);
668 DrawCursor(spriteBatch);
672 private static void DrawCursor(SpriteBatch spriteBatch)
674 if (GameMain.WindowActive && !HideCursor && MouseCursorSprites.Prefabs.Any())
677 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable);
679 if (GameMain.GameSession?.CrewManager is { DraggedOrderPrefab: { SymbolSprite: { } orderSprite, Color: var color }, DragOrder:
true })
681 float spriteSize = Math.Max(orderSprite.size.X, orderSprite.size.Y);
682 orderSprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, color, orderSprite.size / 2f, scale: 32f / spriteSize * Scale);
685 var sprite = MouseCursorSprites[MouseCursor] ?? MouseCursorSprites[
CursorState.Default];
686 sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f);
689 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerState, rasterizerState: GameMain.ScissorTestEnable);
693 public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, Color color, Rectangle? drawArea =
null, SpriteEffects spriteEffects = SpriteEffects.None)
695 Rectangle area = drawArea ??
new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
697 float scale = Math.Max(
698 (
float)area.Width / backgroundSprite.SourceRect.Width,
699 (
float)area.Height / backgroundSprite.SourceRect.Height) * 1.1f;
700 float paddingX = backgroundSprite.SourceRect.Width * scale - area.Width;
701 float paddingY = backgroundSprite.SourceRect.Height * scale - area.Height;
703 double noiseT = Timing.TotalTime * 0.02f;
704 Vector2 pos =
new Vector2((
float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (
float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
705 pos =
new Vector2(pos.X * paddingX, pos.Y * paddingY);
707 spriteBatch.Draw(backgroundSprite.Texture,
708 area.Center.ToVector2() + pos,
709 backgroundSprite.SourceRect, color, 0.0f, backgroundSprite.size / 2,
710 scale, spriteEffects, 0.0f);
714 private static readonly List<GUIComponent> updateList =
new List<GUIComponent>();
716 private static readonly HashSet<GUIComponent> updateListSet =
new HashSet<GUIComponent>();
717 private static readonly Queue<GUIComponent> removals =
new Queue<GUIComponent>();
718 private static readonly Queue<GUIComponent> additions =
new Queue<GUIComponent>();
720 private static readonly List<GUIComponent> firstAdditions =
new List<GUIComponent>();
722 private static readonly List<GUIComponent> lastAdditions =
new List<GUIComponent>();
728 public static void AddToUpdateList(GUIComponent component)
732 if (component ==
null)
734 DebugConsole.ThrowError(
"Trying to add a null component on the GUI update list!");
737 if (!component.Visible) {
return; }
738 if (component.UpdateOrder < 0)
740 firstAdditions.Add(component);
742 else if (component.UpdateOrder > 0)
744 lastAdditions.Add(component);
748 additions.Enqueue(component);
757 public static void RemoveFromUpdateList(GUIComponent component,
bool alsoChildren =
true)
761 if (updateListSet.Contains(component))
763 removals.Enqueue(component);
767 if (component.RectTransform !=
null)
769 component.RectTransform.Children.ForEach(c => RemoveFromUpdateList(c.GUIComponent));
773 component.Children.ForEach(c => RemoveFromUpdateList(c));
779 public static void ClearUpdateList()
788 updateListSet.Clear();
792 private static void RefreshUpdateList()
796 foreach (var component
in updateList)
798 if (!component.Visible)
800 RemoveFromUpdateList(component);
803 ProcessHelperList(firstAdditions);
805 ProcessHelperList(lastAdditions);
810 private static void ProcessAdditions()
814 while (additions.Count > 0)
816 var component = additions.Dequeue();
817 if (!updateListSet.Contains(component))
819 updateList.Add(component);
820 updateListSet.Add(component);
826 private static void ProcessRemovals()
830 while (removals.Count > 0)
832 var component = removals.Dequeue();
833 updateList.Remove(component);
834 updateListSet.Remove(component);
843 private static void ProcessHelperList(List<GUIComponent> list)
847 if (list.Count == 0) {
return; }
848 foreach (var item
in list)
851 if (updateList.Count > 0)
853 index = updateList.Count;
854 while (index > 0 && updateList[index-1].UpdateOrder > item.UpdateOrder)
859 if (!updateListSet.Contains(item))
861 updateList.Insert(index, item);
862 updateListSet.Add(item);
869 private static void HandlePersistingElements(
float deltaTime)
871 bool currentMessageBoxIsVerificationPrompt = GUIMessageBox.VisibleBox is GUIMessageBox { DrawOnTop:
true };
873 if (!currentMessageBoxIsVerificationPrompt)
875 GUIMessageBox.AddActiveToGUIUpdateList();
878 if (SettingsMenuOpen)
880 SettingsMenuContainer.AddToGUIUpdateList();
882 else if (PauseMenuOpen)
884 PauseMenu.AddToGUIUpdateList();
889 GUIContextMenu.AddActiveToGUIUpdateList();
892 if (currentMessageBoxIsVerificationPrompt)
894 GUIMessageBox.VisibleBox.AddToGUIUpdateList();
898 public static IEnumerable<GUIComponent> GetAdditions()
900 return additions.Union(firstAdditions).Union(lastAdditions);
904 public static GUIComponent MouseOn {
get;
private set; }
906 public static bool IsMouseOn(GUIComponent target)
910 if (target ==
null) {
return false; }
912 return target == MouseOn || target.IsParentOf(MouseOn);
916 public static void ForceMouseOn(GUIComponent c)
927 public static GUIComponent UpdateMouseOn()
931 GUIComponent prevMouseOn = MouseOn;
933 int inventoryIndex = -1;
935 Inventory.RefreshMouseOnInventory();
936 if (Inventory.IsMouseOnInventory)
938 inventoryIndex = updateList.IndexOf(
CharacterHUD.HUDFrame);
941 if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) ||
942 (prevMouseOn ==
null && !PlayerInput.SecondaryMouseButtonHeld() && !Inventory.DraggingItems.Any()))
944 for (var i = updateList.Count - 1; i > inventoryIndex; i--)
946 var c = updateList[i];
947 if (!c.CanBeFocused) {
continue; }
948 if (c.MouseRect.Contains(PlayerInput.MousePosition))
950 if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn || prevMouseOn ==
null)
960 MouseOn = prevMouseOn;
963 MouseCursor = UpdateMouseCursorState(MouseOn);
968 private static CursorState UpdateMouseCursorState(GUIComponent c)
974 if (GUIScrollBar.DraggingBar !=
null) {
return GUIScrollBar.DraggingBar.Bar.HoverCursor; }
976 if (SubEditorScreen.IsSubEditor() && SubEditorScreen.DraggedItemPrefab !=
null) {
return CursorState.Hand; }
995 if (c ==
null || c is GUICustomComponent)
997 switch (Screen.Selected)
1003 case GameScreen _ when !(
Character.Controlled?.ShouldLockHud() ??
true):
1004 if (
CharacterHUD.MouseOnCharacterPortrait() || CharacterHealth.IsMouseOnHealthBar())
1010 case SubEditorScreen editor:
1012 if (MapEntity.StartMovingPos != Vector2.Zero || MapEntity.Resizing)
1016 if (MapEntity.HighlightedEntities.Any(h => !h.IsSelected))
1025 if (c !=
null && c.Visible)
1027 if (c.AlwaysOverrideCursor) {
return c.HoverCursor; }
1032 var monitorRect =
new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
1034 var parent = FindInteractParent(c);
1038 var dragHandle = c as GUIDragHandle ?? parent as GUIDragHandle;
1039 if (dragHandle !=
null)
1044 if (c is GUIListBox && (parent ==
null || parent == c))
1050 if (parent is not GUIButton && parent is not GUIListBox ||
1051 (c is GUIButton) || (c is GUITickBox))
1053 if (!c.Rect.Equals(monitorRect))
1055 if (c is GUITickBox)
1058 if (c.State is GUIComponent.ComponentState.Hover or GUIComponent.ComponentState.HoverSelected)
1060 return c.HoverCursor;
1065 return c.HoverCursor;
1075 if (parent is GUIListBox listBox && c.Parent == listBox.Content)
1077 if (listBox.DraggedElement !=
null) {
return CursorState.Dragging; }
1078 if (listBox.CurrentDragMode != GUIListBox.DragMode.NoDragging) {
return CursorState.Move; }
1082 var hoverParent = c;
1085 if (hoverParent == parent || hoverParent ==
null) {
break; }
1086 if (hoverParent.State == GUIComponent.ComponentState.Hover) {
return CursorState.Hand; }
1087 hoverParent = hoverParent.Parent;
1092 if (parent !=
null && parent.CanBeFocused)
1094 if (!parent.Rect.Equals(monitorRect)) {
return parent.HoverCursor; }
1098 if (Inventory.IsMouseOnInventory) {
return Inventory.GetInventoryMouseCursor(); }
1102 if (character !=
null)
1105 if (character.CharacterHealth.MouseOnElement) {
return CursorState.Hand; }
1107 if (character.SelectedCharacter !=
null)
1109 if (character.SelectedCharacter.CharacterHealth.MouseOnElement)
1116 if (character.FocusedItem !=
null) {
return CursorState.Hand; }
1121 static GUIComponent FindInteractParent(GUIComponent component)
1125 var parent = component.Parent;
1126 if (parent ==
null) {
return null; }
1128 if (ContainsMouse(parent))
1134 case GUIButton button:
1136 case GUITextBox box:
1138 case GUIListBox list:
1140 case GUIScrollBar bar:
1142 case GUIDragHandle dragHandle:
1155 static bool ContainsMouse(GUIComponent component)
1158 return !component.MouseRect.Equals(
Rectangle.Empty) ?
1159 component.MouseRect.Contains(PlayerInput.MousePosition) :
1160 component.Rect.Contains(PlayerInput.MousePosition);
1169 public static void SetCursorWaiting(
int waitSeconds = 10, Func<bool> endCondition =
null)
1171 CoroutineManager.StartCoroutine(WaitCursorCoroutine(),
"WaitCursorTimeout");
1173 IEnumerable<CoroutineStatus> WaitCursorCoroutine()
1176 var timeOut = DateTime.Now +
new TimeSpan(0, 0, waitSeconds);
1177 while (DateTime.Now < timeOut)
1179 if (endCondition !=
null)
1183 if (endCondition.Invoke()) {
break; }
1187 yield
return CoroutineStatus.Running;
1190 yield
return CoroutineStatus.Success;
1194 public static void ClearCursorWait()
1198 CoroutineManager.StopCoroutines(
"WaitCursorTimeout");
1203 public static bool HasSizeChanged(Point referenceResolution,
float referenceUIScale,
float referenceHUDScale)
1205 return GameMain.GraphicsWidth != referenceResolution.X || GameMain.GraphicsHeight != referenceResolution.Y ||
1206 referenceUIScale != Inventory.UIScale || referenceHUDScale != Scale;
1209 public static void Update(
float deltaTime)
1213 if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.S))
1215 debugDrawSounds = !debugDrawSounds;
1217 if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.E))
1219 debugDrawEvents = !debugDrawEvents;
1221 if (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.M))
1223 debugDrawMetaData.Enabled = !debugDrawMetaData.Enabled;
1226 if (debugDrawMetaData.Enabled)
1228 if (PlayerInput.KeyHit(Keys.Up))
1230 debugDrawMetaData.Offset--;
1232 if (PlayerInput.KeyHit(Keys.Down))
1234 debugDrawMetaData.Offset++;
1236 if (PlayerInput.IsCtrlDown())
1238 if (PlayerInput.KeyHit(Keys.D1))
1240 debugDrawMetaData.FactionMetadata = !debugDrawMetaData.FactionMetadata;
1241 debugDrawMetaData.Offset = 0;
1243 if (PlayerInput.KeyHit(Keys.D2))
1245 debugDrawMetaData.UpgradeLevels = !debugDrawMetaData.UpgradeLevels;
1246 debugDrawMetaData.Offset = 0;
1248 if (PlayerInput.KeyHit(Keys.D3))
1250 debugDrawMetaData.UpgradePrices = !debugDrawMetaData.UpgradePrices;
1251 debugDrawMetaData.Offset = 0;
1256 HandlePersistingElements(deltaTime);
1257 RefreshUpdateList();
1259 Debug.Assert(updateList.Count == updateListSet.Count);
1260 foreach (var c
in updateList)
1262 c.UpdateAuto(deltaTime);
1264 UpdateMessages(deltaTime);
1265 UpdateSavingIndicator(deltaTime);
1269 public static void UpdateGUIMessageBoxesOnly(
float deltaTime)
1271 GUIMessageBox.AddActiveToGUIUpdateList();
1272 RefreshUpdateList();
1274 foreach (var c
in updateList)
1276 c.UpdateAuto(deltaTime);
1280 private static void UpdateMessages(
float deltaTime)
1284 foreach (GUIMessage msg
in messages)
1286 if (msg.WorldSpace) {
continue; }
1287 msg.Timer -= deltaTime;
1289 if (msg.Size.X > HUDLayoutSettings.MessageAreaTop.Width)
1291 msg.Pos = Vector2.Lerp(Vector2.Zero,
new Vector2(-HUDLayoutSettings.MessageAreaTop.Width - msg.Size.X, 0), 1.0f - msg.Timer / msg.LifeTime);
1296 if (msg.Timer > 1.0f)
1298 msg.Pos = Vector2.Lerp(msg.Pos,
new Vector2(-HUDLayoutSettings.MessageAreaTop.Width / 2 - msg.Size.X / 2, 0), Math.Min(deltaTime * 10.0f, 1.0f));
1302 msg.Pos = Vector2.Lerp(msg.Pos,
new Vector2(-HUDLayoutSettings.MessageAreaTop.Width - msg.Size.X, 0), deltaTime * 10.0f);
1309 foreach (GUIMessage msg
in messages)
1311 if (!msg.WorldSpace) {
continue; }
1312 msg.Timer -= deltaTime;
1313 msg.Pos += msg.Velocity * deltaTime;
1316 messages.RemoveAll(m => m.Timer <= 0.0f);
1320 private static void UpdateSavingIndicator(
float deltaTime)
1322 if (GUIStyle.SavingIndicator ==
null) {
return; }
1325 if (timeUntilSavingIndicatorDisabled.HasValue)
1327 timeUntilSavingIndicatorDisabled -= deltaTime;
1328 if (timeUntilSavingIndicatorDisabled <= 0.0f)
1330 isSavingIndicatorEnabled =
false;
1331 timeUntilSavingIndicatorDisabled =
null;
1334 if (isSavingIndicatorEnabled)
1336 if (savingIndicatorColor == Color.Transparent)
1338 savingIndicatorState = SavingIndicatorState.FadingIn;
1339 savingIndicatorColorLerpAmount = 0.0f;
1341 else if (savingIndicatorColor == Color.White)
1343 savingIndicatorState = SavingIndicatorState.None;
1348 if (savingIndicatorColor == Color.White)
1350 savingIndicatorState = SavingIndicatorState.FadingOut;
1351 savingIndicatorColorLerpAmount = 0.0f;
1353 else if (savingIndicatorColor == Color.Transparent)
1355 savingIndicatorState = SavingIndicatorState.None;
1358 if (savingIndicatorState != SavingIndicatorState.None)
1360 bool isFadingIn = savingIndicatorState == SavingIndicatorState.FadingIn;
1361 Color lerpStartColor = isFadingIn ? Color.Transparent : Color.White;
1362 Color lerpTargetColor = isFadingIn ? Color.White : Color.Transparent;
1363 savingIndicatorColorLerpAmount += (isFadingIn ? 2.0f : 0.5f) * deltaTime;
1364 savingIndicatorColor = Color.Lerp(lerpStartColor, lerpTargetColor, savingIndicatorColorLerpAmount);
1366 if (IsSavingIndicatorVisible)
1368 savingIndicatorSpriteIndex = (savingIndicatorSpriteIndex + 15.0f * deltaTime) % (GUIStyle.SavingIndicator.FrameCount + 1);
1373 #region Element drawing
1375 private static readonly List<float> usedIndicatorAngles =
new List<float>();
1379 public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Range<float> visibleRange, Sprite sprite, in Color color,
1380 bool createOffset =
true,
float scaleMultiplier = 1.0f,
float? overrideAlpha =
null, LocalizedString label =
null)
1382 Vector2 diff = worldPosition - cam.WorldViewCenter;
1383 float dist = diff.Length();
1385 float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale;
1387 if (overrideAlpha.HasValue || visibleRange.Contains(dist))
1389 float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.Start) / 100.0f, 1.0f - ((dist - visibleRange.End + 100f) / 100.0f), 1.0f);
1390 Vector2 targetScreenPos = cam.WorldToScreen(worldPosition);
1394 sprite.Draw(spriteBatch, targetScreenPos, color * alpha, rotate: 0.0f, scale: symbolScale);
1398 float screenDist = Vector2.Distance(cam.WorldToScreen(cam.WorldViewCenter), targetScreenPos);
1399 float angle = MathUtils.VectorToAngle(diff);
1400 float originalAngle = angle;
1402 const float minAngleDiff = 0.05f;
1403 bool overlapFound =
true;
1405 while (overlapFound && iterations < 10)
1407 overlapFound =
false;
1408 foreach (
float usedIndicatorAngle
in usedIndicatorAngles)
1410 float shortestAngle = MathUtils.GetShortestAngle(angle, usedIndicatorAngle);
1411 if (MathUtils.NearlyEqual(shortestAngle, 0.0f)) { shortestAngle = 0.01f; }
1412 if (Math.Abs(shortestAngle) < minAngleDiff)
1414 angle -= Math.Sign(shortestAngle) * (minAngleDiff - Math.Abs(shortestAngle));
1415 overlapFound =
true;
1422 usedIndicatorAngles.Add(angle);
1424 Vector2 iconDiff =
new Vector2(
1425 (
float)Math.Cos(angle) * Math.Min(GameMain.GraphicsWidth * 0.4f, screenDist + 10),
1426 (
float)-Math.Sin(angle) * Math.Min(GameMain.GraphicsHeight * 0.4f, screenDist + 10));
1428 angle = MathHelper.Lerp(originalAngle, angle, MathHelper.Clamp(((screenDist + 10f) - iconDiff.Length()) / 10f, 0f, 1f));
1430 iconDiff =
new Vector2(
1431 (
float)Math.Cos(angle) * Math.Min(GameMain.GraphicsWidth * 0.4f, screenDist),
1432 (
float)-Math.Sin(angle) * Math.Min(GameMain.GraphicsHeight * 0.4f, screenDist));
1434 Vector2 iconPos = cam.WorldToScreen(cam.WorldViewCenter) + iconDiff;
1435 sprite.Draw(spriteBatch, iconPos, color * alpha, rotate: 0.0f, scale: symbolScale);
1439 float cursorDist = Vector2.Distance(PlayerInput.MousePosition, iconPos);
1440 if (cursorDist < sprite.size.X * symbolScale)
1442 Vector2 textSize = GUIStyle.Font.MeasureString(label);
1443 Vector2 textPos = iconPos +
new Vector2(sprite.size.X * symbolScale * 0.7f * Math.Sign(-iconDiff.X), -textSize.Y / 2);
1444 if (iconDiff.X > 0) { textPos.X -= textSize.X; }
1445 DrawString(spriteBatch, textPos + Vector2.One, label, Color.Black);
1446 DrawString(spriteBatch, textPos, label, color);
1450 if (screenDist - 10 > iconDiff.Length())
1452 Vector2 normalizedDiff = Vector2.Normalize(targetScreenPos - iconPos);
1453 Vector2 arrowOffset = normalizedDiff * sprite.size.X * symbolScale * 0.7f;
1454 Arrow.Draw(spriteBatch, iconPos + arrowOffset, color * alpha, MathUtils.VectorToAngle(arrowOffset) + MathHelper.PiOver2, scale: 0.5f);
1459 public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam,
float hideDist, Sprite sprite, Color color,
1460 bool createOffset =
true,
float scaleMultiplier = 1.0f,
float? overrideAlpha =
null)
1462 DrawIndicator(spriteBatch, worldPosition, cam,
new Range<float>(hideDist,
float.PositiveInfinity), sprite, color, createOffset, scaleMultiplier, overrideAlpha);
1465 public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr,
float depth = 0.0f,
float width = 1)
1467 DrawLine(sb, solidWhiteTexture, start, end, clr, depth, (
int)width);
1470 public static void DrawLine(SpriteBatch sb, Sprite sprite, Vector2 start, Vector2 end, Color clr,
float depth = 0.0f,
int width = 1)
1472 Vector2 edge = end - start;
1474 float angle = (float)Math.Atan2(edge.Y, edge.X);
1476 sb.Draw(sprite.Texture,
1485 new Vector2(0, sprite.SourceRect.Height / 2),
1490 public static void DrawLine(SpriteBatch sb, Texture2D texture, Vector2 start, Vector2 end, Color clr,
float depth = 0.0f,
int width = 1)
1492 Vector2 edge = end - start;
1494 float angle = (float)Math.Atan2(edge.Y, edge.X);
1505 new Vector2(0, texture.Height / 2.0f),
1510 public static void DrawString(SpriteBatch sb, Vector2 pos, LocalizedString text, Color color, Color? backgroundColor =
null,
int backgroundPadding = 0, GUIFont font =
null,
ForceUpperCase forceUpperCase =
ForceUpperCase.Inherit)
1512 DrawString(sb, pos, text.Value, color, backgroundColor, backgroundPadding, font, forceUpperCase);
1515 public static void DrawString(SpriteBatch sb, Vector2 pos,
string text, Color color, Color? backgroundColor =
null,
int backgroundPadding = 0, GUIFont font =
null,
ForceUpperCase forceUpperCase =
ForceUpperCase.Inherit)
1517 if (color.A == 0) {
return; }
1518 if (font ==
null) { font = GUIStyle.Font; }
1519 if (backgroundColor !=
null && backgroundColor.Value.A > 0)
1521 Vector2 textSize = font.MeasureString(text);
1522 DrawRectangle(sb, pos - Vector2.One * backgroundPadding, textSize + Vector2.One * 2.0f * backgroundPadding, (Color)backgroundColor,
true);
1525 font.DrawString(sb, text, pos, color, forceUpperCase: forceUpperCase);
1528 public static void DrawStringWithColors(SpriteBatch sb, Vector2 pos,
string text, Color color, in ImmutableArray<RichTextData>? richTextData, Color? backgroundColor =
null,
int backgroundPadding = 0, GUIFont font =
null,
float depth = 0.0f)
1530 if (font ==
null) font = GUIStyle.Font;
1531 if (backgroundColor !=
null)
1533 Vector2 textSize = font.MeasureString(text);
1534 DrawRectangle(sb, pos - Vector2.One * backgroundPadding, textSize + Vector2.One * 2.0f * backgroundPadding, (Color)backgroundColor,
true, depth, 5);
1537 font.DrawStringWithColors(sb, text, pos, color, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, depth, richTextData);
1540 private const int DonutSegments = 30;
1541 private static readonly ImmutableArray<Vector2> canonicalCircle
1542 = Enumerable.Range(0, DonutSegments)
1543 .Select(i => i * (2.0f * MathF.PI / DonutSegments))
1544 .Select(angle =>
new Vector2(MathF.Cos(angle), MathF.Sin(angle)))
1545 .ToImmutableArray();
1546 private static readonly VertexPositionColorTexture[] donutVerts =
new VertexPositionColorTexture[DonutSegments * 4];
1548 public static void DrawDonutSection(
1549 SpriteBatch sb, Vector2 center, Range<float> radii,
float sectionRad, Color clr,
float depth = 0.0f,
float rotationRad = 0.0f)
1551 float getRadius(
int vertexIndex)
1552 => (vertexIndex % 4)
switch
1558 _ =>
throw new InvalidOperationException()
1560 static int getDirectionIndex(
int vertexIndex)
1561 => (vertexIndex % 4)
switch
1563 0 => (vertexIndex / 4) + 0,
1564 1 => (vertexIndex / 4) + 1,
1565 2 => (vertexIndex / 4) + 0,
1566 3 => (vertexIndex / 4) + 1,
1567 _ =>
throw new InvalidOperationException()
1570 float sectionProportion = sectionRad / (MathF.PI * 2.0f);
1571 int maxDirectionIndex = Math.Min(DonutSegments, (
int)MathF.Ceiling(sectionProportion * DonutSegments));
1573 Vector2 getDirection(
int vertexIndex)
1575 int directionIndex = getDirectionIndex(vertexIndex);
1576 Vector2 dir = canonicalCircle[directionIndex % DonutSegments];
1577 if (maxDirectionIndex > 0 && directionIndex >= maxDirectionIndex)
1579 float maxSectionProportion = (float)maxDirectionIndex / DonutSegments;
1581 canonicalCircle[maxDirectionIndex - 1],
1582 canonicalCircle[maxDirectionIndex % DonutSegments],
1583 1.0f - (maxSectionProportion - sectionProportion) * DonutSegments);
1586 return new Vector2(dir.Y, -dir.X);
1589 for (
int vertexIndex = 0; vertexIndex < maxDirectionIndex * 4; vertexIndex++)
1591 donutVerts[vertexIndex].Color = clr;
1592 donutVerts[vertexIndex].Position =
new Vector3(center + Vector2.Transform(getDirection(vertexIndex) * getRadius(vertexIndex), Matrix.CreateRotationZ(rotationRad)), 0.0f);
1594 sb.Draw(solidWhiteTexture, donutVerts, depth, count: maxDirectionIndex);
1597 public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr,
bool isFilled =
false,
float depth = 0.0f,
float thickness = 1)
1609 DrawRectangle(sb,
new Rectangle((
int)start.X, (
int)start.Y, (
int)size.X, (
int)size.Y), clr, isFilled, depth, thickness);
1612 public static void DrawRectangle(SpriteBatch sb, Rectangle rect, Color clr,
bool isFilled =
false,
float depth = 0.0f,
float thickness = 1)
1616 sb.Draw(solidWhiteTexture, rect,
null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
1621 sb.Draw(solidWhiteTexture,
new Vector2(rect.X, rect.Y), srcRect, clr, 0.0f, Vector2.Zero,
new Vector2(thickness, rect.Height), SpriteEffects.None, depth);
1622 sb.Draw(solidWhiteTexture,
new Vector2(rect.X + thickness, rect.Y), srcRect, clr, 0.0f, Vector2.Zero,
new Vector2(rect.Width - thickness, thickness), SpriteEffects.None, depth);
1623 sb.Draw(solidWhiteTexture,
new Vector2(rect.X + thickness, rect.Bottom - thickness), srcRect, clr, 0.0f, Vector2.Zero,
new Vector2(rect.Width - thickness, thickness), SpriteEffects.None, depth);
1624 sb.Draw(solidWhiteTexture,
new Vector2(rect.Right - thickness, rect.Y + thickness), srcRect, clr, 0.0f, Vector2.Zero,
new Vector2(thickness, rect.Height - thickness * 2f), SpriteEffects.None, depth);
1628 public static void DrawRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 origin,
float rotation, Color clr,
float depth = 0.0f,
float thickness = 1, OutlinePosition outlinePos = OutlinePosition.Centered)
1630 Vector2 topLeft =
new Vector2(-origin.X, -origin.Y);
1631 Vector2 topRight =
new Vector2(-origin.X + size.X, -origin.Y);
1632 Vector2 bottomLeft =
new Vector2(-origin.X, -origin.Y + size.Y);
1633 Vector2 actualSize = size;
1637 case OutlinePosition.Default:
1638 actualSize +=
new Vector2(thickness);
1640 case OutlinePosition.Centered:
1641 topLeft -=
new Vector2(thickness * 0.5f);
1642 topRight -=
new Vector2(thickness * 0.5f);
1643 bottomLeft -=
new Vector2(thickness * 0.5f);
1644 actualSize +=
new Vector2(thickness);
1646 case OutlinePosition.Inside:
1647 topRight -=
new Vector2(thickness, 0.0f);
1648 bottomLeft -=
new Vector2(0.0f, thickness);
1650 case OutlinePosition.Outside:
1651 topLeft -=
new Vector2(thickness);
1652 topRight -=
new Vector2(0.0f, thickness);
1653 bottomLeft -=
new Vector2(thickness, 0.0f);
1654 actualSize +=
new Vector2(thickness * 2.0f);
1658 Matrix rotate = Matrix.CreateRotationZ(rotation);
1659 topLeft = Vector2.Transform(topLeft, rotate) + position;
1660 topRight = Vector2.Transform(topRight, rotate) + position;
1661 bottomLeft = Vector2.Transform(bottomLeft, rotate) + position;
1664 sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero,
new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
1665 sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero,
new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
1666 sb.Draw(solidWhiteTexture, topRight, srcRect, clr, rotation, Vector2.Zero,
new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
1667 sb.Draw(solidWhiteTexture, bottomLeft, srcRect, clr, rotation, Vector2.Zero,
new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
1670 public static void DrawFilledRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 pivot,
float rotation, Color clr,
float depth = 0.0f)
1673 sb.Draw(solidWhiteTexture, position, srcRect, clr, rotation, (pivot/size), size, SpriteEffects.None, depth);
1676 public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr,
float depth = 0.0f)
1678 DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth);
1681 public static void DrawFilledRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr,
float depth = 0.0f)
1694 sb.Draw(solidWhiteTexture, start,
null, clr, 0f, Vector2.Zero, size, SpriteEffects.None, depth);
1697 public static void DrawRectangle(SpriteBatch sb, Vector2 center,
float width,
float height,
float rotation, Color clr,
float depth = 0.0f,
float thickness = 1)
1699 Matrix rotate = Matrix.CreateRotationZ(rotation);
1703 Vector2 topLeft = center + Vector2.Transform(
new Vector2(-width, -height), rotate);
1704 Vector2 topRight = center + Vector2.Transform(
new Vector2(width, -height), rotate);
1705 Vector2 bottomLeft = center + Vector2.Transform(
new Vector2(-width, height), rotate);
1706 Vector2 bottomRight = center + Vector2.Transform(
new Vector2(width, height), rotate);
1708 DrawLine(sb, topLeft, topRight, clr, depth, thickness);
1709 DrawLine(sb, topRight, bottomRight, clr, depth, thickness);
1710 DrawLine(sb, bottomRight, bottomLeft, clr, depth, thickness);
1711 DrawLine(sb, bottomLeft, topLeft, clr, depth, thickness);
1714 public static void DrawRectangle(SpriteBatch sb, Vector2[] corners, Color clr,
float depth = 0.0f,
float thickness = 1)
1716 if (corners.Length != 4)
1718 throw new Exception(
"Invalid length of the corners array! Must be 4");
1720 DrawLine(sb, corners[0], corners[1], clr, depth, thickness);
1721 DrawLine(sb, corners[1], corners[2], clr, depth, thickness);
1722 DrawLine(sb, corners[2], corners[3], clr, depth, thickness);
1723 DrawLine(sb, corners[3], corners[0], clr, depth, thickness);
1726 public static void DrawProgressBar(SpriteBatch sb, Vector2 start, Vector2 size,
float progress, Color clr,
float depth = 0.0f)
1728 DrawProgressBar(sb, start, size, progress, clr,
new Color(0.5f, 0.57f, 0.6f, 1.0f), depth);
1731 public static void DrawProgressBar(SpriteBatch sb, Vector2 start, Vector2 size,
float progress, Color clr, Color outlineColor,
float depth = 0.0f)
1733 DrawRectangle(sb,
new Vector2(start.X, -start.Y), size, outlineColor,
false, depth);
1736 DrawRectangle(sb,
new Rectangle((
int)start.X + padding, -(
int)(start.Y - padding), (
int)((size.X - padding * 2) * progress), (
int)size.Y - padding * 2),
1740 public static bool DrawButton(SpriteBatch sb, Rectangle rect,
string text, Color color,
bool isHoldable =
false)
1742 bool clicked =
false;
1744 if (rect.Contains(PlayerInput.MousePosition))
1746 clicked = PlayerInput.PrimaryMouseButtonHeld();
1749 new Color((
int)(color.R * 0.8f), (
int)(color.G * 0.8f), (
int)(color.B * 0.8f), color.A) :
1750 new Color((int)(color.R * 1.2f), (int)(color.G * 1.2f), (int)(color.B * 1.2f), color.A);
1752 if (!isHoldable) clicked = PlayerInput.PrimaryMouseButtonClicked();
1755 DrawRectangle(sb, rect, color,
true);
1760 origin = GUIStyle.Font.MeasureString(text) / 2;
1764 origin = Vector2.Zero;
1767 GUIStyle.Font.DrawString(sb, text,
new Vector2(rect.Center.X, rect.Center.Y), Color.White, 0.0f, origin, 1.0f, SpriteEffects.None, 0.0f);
1772 private static void DrawMessages(SpriteBatch spriteBatch, Camera cam)
1774 if (messages.Count == 0) {
return; }
1776 bool useScissorRect = messages.Any(m => !m.WorldSpace);
1777 Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
1781 spriteBatch.GraphicsDevice.ScissorRectangle = HUDLayoutSettings.MessageAreaTop;
1782 spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
1785 foreach (GUIMessage msg
in messages)
1787 if (msg.WorldSpace) {
continue; }
1789 Vector2 drawPos =
new Vector2(HUDLayoutSettings.MessageAreaTop.Right, HUDLayoutSettings.MessageAreaTop.Center.Y);
1791 msg.Font.DrawString(spriteBatch, msg.Text, drawPos + msg.DrawPos + Vector2.One, Color.Black, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
1792 msg.Font.DrawString(spriteBatch, msg.Text, drawPos + msg.DrawPos, msg.Color, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
1799 spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
1800 spriteBatch.Begin(SpriteSortMode.Deferred);
1803 foreach (GUIMessage msg
in messages)
1805 if (!msg.WorldSpace) {
continue; }
1810 if (msg.Timer < 1.0f) { alpha -= 1.0f - msg.Timer; }
1812 Vector2 drawPos = cam.WorldToScreen(msg.DrawPos);
1813 msg.Font.DrawString(spriteBatch, msg.Text, drawPos + Vector2.One, Color.Black * alpha, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
1814 msg.Font.DrawString(spriteBatch, msg.Text, drawPos, msg.Color * alpha, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
1818 messages.RemoveAll(m => m.Timer <= 0.0f);
1824 public static void DrawBezierWithDots(SpriteBatch spriteBatch, Vector2 start, Vector2 end, Vector2 control,
int pointCount, Color color,
int dotSize = 2)
1826 for (
int i = 0; i < pointCount; i++)
1828 float t = (float)i / (pointCount - 1);
1829 Vector2 pos = MathUtils.Bezier(start, control, end, t);
1830 ShapeExtensions.DrawPoint(spriteBatch, pos, color, dotSize);
1834 public static void DrawSineWithDots(SpriteBatch spriteBatch, Vector2 from, Vector2 dir,
float amplitude,
float length,
float scale,
int pointCount, Color color,
int dotSize = 2)
1836 Vector2 up = dir.Right();
1839 for (
int i = 0; i < pointCount; i++)
1844 float t = (float)i / (pointCount - 1);
1845 float sin = (float)Math.Sin(t / length * scale) * amplitude;
1846 pos += (up * sin) + (dir * t);
1848 ShapeExtensions.DrawPoint(spriteBatch, pos, color, dotSize);
1852 private static void DrawSavingIndicator(SpriteBatch spriteBatch)
1854 if (!IsSavingIndicatorVisible || GUIStyle.SavingIndicator ==
null) {
return; }
1855 var sheet = GUIStyle.SavingIndicator;
1856 Vector2 pos =
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) -
new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
1857 sheet.Draw(spriteBatch, (
int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale:
new Vector2(Scale));
1860 public static void DrawCapsule(SpriteBatch sb, Vector2 origin,
float length,
float radius,
float rotation, Color clr,
float depth = 0,
float thickness = 1)
1862 DrawDonutSection(sb, origin + Vector2.Transform(-
new Vector2(length / 2, 0), Matrix.CreateRotationZ(rotation)),
new Range<float>(radius - thickness / 2, radius + thickness / 2), MathHelper.Pi, clr, depth, rotation - MathHelper.Pi);
1863 DrawRectangle(sb, origin,
new Vector2(length, radius * 2),
new Vector2(length / 2, radius), rotation, clr, depth, thickness);
1864 DrawDonutSection(sb, origin + Vector2.Transform(
new Vector2(length / 2, 0), Matrix.CreateRotationZ(rotation)),
new Range<float>(radius - thickness / 2, radius + thickness / 2), MathHelper.Pi, clr, depth, rotation);
1868 #region Element creation
1870 public static Texture2D CreateCircle(
int radius,
bool filled =
false)
1872 int outerRadius = radius * 2 + 2;
1874 Color[] data =
new Color[outerRadius * outerRadius];
1877 for (
int i = 0; i < data.Length; i++)
1878 data[i] = Color.Transparent;
1882 float diameterSqr = radius * radius;
1883 for (
int x = 0; x < outerRadius; x++)
1885 for (
int y = 0; y < outerRadius; y++)
1887 Vector2 pos =
new Vector2(radius - x, radius - y);
1888 if (pos.LengthSquared() <= diameterSqr)
1890 TrySetArray(data, y * outerRadius + x + 1, Color.White);
1898 double angleStep = 1f / radius;
1900 for (
double angle = 0; angle < Math.PI * 2; angle += angleStep)
1903 int x = (int)Math.Round(radius + radius * Math.Cos(angle));
1904 int y = (int)Math.Round(radius + radius * Math.Sin(angle));
1906 TrySetArray(data, y * outerRadius + x + 1, Color.White);
1910 Texture2D texture =
null;
1911 CrossThread.RequestExecutionOnMainThread(() =>
1913 texture =
new Texture2D(GraphicsDevice, outerRadius, outerRadius);
1914 texture.SetData(data);
1919 public static Texture2D CreateCapsule(
int radius,
int height)
1921 int textureWidth = Math.Max(radius * 2, 1);
1922 int textureHeight = Math.Max(height + radius * 2, 1);
1924 Color[] data =
new Color[textureWidth * textureHeight];
1927 for (
int i = 0; i < data.Length; i++)
1928 data[i] = Color.Transparent;
1931 double angleStep = 1f / radius;
1933 for (
int i = 0; i < 2; i++)
1935 for (
double angle = 0; angle < Math.PI * 2; angle += angleStep)
1938 int x = (int)Math.Round(radius + radius * Math.Cos(angle));
1939 int y = (height - 1) * i + (
int)Math.Round(radius + radius * Math.Sin(angle));
1941 TrySetArray(data, y * textureWidth + x, Color.White);
1945 for (
int y = radius; y < textureHeight - radius; y++)
1947 TrySetArray(data, y * textureWidth, Color.White);
1948 TrySetArray(data, y * textureWidth + (textureWidth - 1), Color.White);
1951 Texture2D texture =
null;
1952 CrossThread.RequestExecutionOnMainThread(() =>
1954 texture =
new Texture2D(GraphicsDevice, textureWidth, textureHeight);
1955 texture.SetData(data);
1960 public static Texture2D CreateRectangle(
int width,
int height)
1962 width = Math.Max(width, 1);
1963 height = Math.Max(height, 1);
1964 Color[] data =
new Color[width * height];
1966 for (
int i = 0; i < data.Length; i++)
1967 data[i] = Color.Transparent;
1969 for (
int y = 0; y < height; y++)
1971 TrySetArray(data, y * width, Color.White);
1972 TrySetArray(data, y * width + (width - 1), Color.White);
1975 for (
int x = 0; x < width; x++)
1977 TrySetArray(data, x, Color.White);
1978 TrySetArray(data, (height - 1) * width + x, Color.White);
1981 Texture2D texture =
null;
1982 CrossThread.RequestExecutionOnMainThread(() =>
1984 texture =
new Texture2D(GraphicsDevice, width, height);
1985 texture.SetData(data);
1990 private static bool TrySetArray(Color[] data,
int index, Color value)
1992 if (index >= 0 && index < data.Length)
1994 data[index] = value;
2006 public static List<GUIButton> CreateButtons(
int count, Vector2 relativeSize, RectTransform parent,
2007 Anchor anchor =
Anchor.TopLeft,
Pivot? pivot =
null, Point? minSize =
null, Point? maxSize =
null,
2008 int absoluteSpacing = 0,
float relativeSpacing = 0, Func<int, int> extraSpacing =
null,
2009 int startOffsetAbsolute = 0,
float startOffsetRelative = 0,
bool isHorizontal =
false,
2010 Alignment textAlignment = Alignment.Center,
string style =
"")
2012 Func<RectTransform, GUIButton> constructor = rectT =>
new GUIButton(rectT,
string.Empty, textAlignment, style);
2013 return CreateElements(count, relativeSize, parent, constructor, anchor, pivot, minSize, maxSize, absoluteSpacing, relativeSpacing, extraSpacing, startOffsetAbsolute, startOffsetRelative, isHorizontal);
2019 public static List<GUIButton> CreateButtons(
int count, Point absoluteSize, RectTransform parent,
2021 int absoluteSpacing = 0,
float relativeSpacing = 0, Func<int, int> extraSpacing =
null,
2022 int startOffsetAbsolute = 0,
float startOffsetRelative = 0,
bool isHorizontal =
false,
2023 Alignment textAlignment = Alignment.Center,
string style =
"")
2025 Func<RectTransform, GUIButton> constructor = rectT =>
new GUIButton(rectT,
string.Empty, textAlignment, style);
2026 return CreateElements(count, absoluteSize, parent, constructor, anchor, pivot, absoluteSpacing, relativeSpacing, extraSpacing, startOffsetAbsolute, startOffsetRelative, isHorizontal);
2032 public static List<T> CreateElements<T>(
int count, Vector2 relativeSize, RectTransform parent, Func<RectTransform, T> constructor,
2033 Anchor anchor =
Anchor.TopLeft,
Pivot? pivot =
null, Point? minSize =
null, Point? maxSize =
null,
2034 int absoluteSpacing = 0,
float relativeSpacing = 0, Func<int, int> extraSpacing =
null,
2035 int startOffsetAbsolute = 0,
float startOffsetRelative = 0,
bool isHorizontal =
false)
2036 where T : GUIComponent
2038 return CreateElements(count, parent, constructor, relativeSize,
null, anchor, pivot, minSize, maxSize, absoluteSpacing, relativeSpacing, extraSpacing, startOffsetAbsolute, startOffsetRelative, isHorizontal);
2044 public static List<T> CreateElements<T>(
int count, Point absoluteSize, RectTransform parent, Func<RectTransform, T> constructor,
2046 int absoluteSpacing = 0,
float relativeSpacing = 0, Func<int, int> extraSpacing =
null,
2047 int startOffsetAbsolute = 0,
float startOffsetRelative = 0,
bool isHorizontal =
false)
2048 where T : GUIComponent
2050 return CreateElements(count, parent, constructor,
null, absoluteSize, anchor, pivot,
null,
null, absoluteSpacing, relativeSpacing, extraSpacing, startOffsetAbsolute, startOffsetRelative, isHorizontal);
2053 public static GUIComponent CreateEnumField(Enum value,
int elementHeight, LocalizedString name, RectTransform parent,
string toolTip =
null, GUIFont font =
null)
2055 font = font ?? GUIStyle.SmallFont;
2056 var frame =
new GUIFrame(
new RectTransform(
new Point(parent.Rect.Width, elementHeight), parent), color: Color.Transparent);
2057 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1), frame.RectTransform), name, font: font)
2061 GUIDropDown enumDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.4f, 1), frame.RectTransform,
Anchor.TopRight),
2062 elementCount: Enum.GetValues(value.GetType()).Length)
2066 foreach (
object enumValue
in Enum.GetValues(value.GetType()))
2068 enumDropDown.AddItem(enumValue.ToString(), enumValue);
2070 enumDropDown.SelectItem(value);
2074 public static GUIComponent CreateRectangleField(Rectangle value,
int elementHeight, LocalizedString name, RectTransform parent, LocalizedString toolTip =
null, GUIFont font =
null)
2076 var frame =
new GUIFrame(
new RectTransform(
new Point(parent.Rect.Width, Math.Max(elementHeight, 26)), parent), color: Color.Transparent);
2077 font = font ?? GUIStyle.SmallFont;
2078 new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 1), frame.RectTransform), name, font: font)
2082 var inputArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.8f, 1), frame.RectTransform,
Anchor.TopRight), isHorizontal:
true, childAnchor:
Anchor.CenterRight)
2085 RelativeSpacing = 0.01f
2087 for (
int i = 3; i >= 0; i--)
2089 var element =
new GUIFrame(
new RectTransform(
new Vector2(0.22f, 1), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point(150, 50) }, style:
null);
2090 new GUITextBlock(
new RectTransform(
new Vector2(0.3f, 1), element.RectTransform,
Anchor.CenterLeft), RectComponentLabels[i], font: font, textAlignment: Alignment.CenterLeft);
2091 GUINumberInput numberInput =
new GUINumberInput(
new RectTransform(
new Vector2(0.7f, 1), element.RectTransform,
Anchor.CenterRight),
2097 numberInput.MinValueInt = 0;
2099 numberInput.MaxValueInt = 9999;
2103 numberInput.IntValue = value.X;
2106 numberInput.IntValue = value.Y;
2109 numberInput.IntValue = value.Width;
2112 numberInput.IntValue = value.Height;
2119 public static GUIComponent CreatePointField(Point value,
int elementHeight, LocalizedString displayName, RectTransform parent, LocalizedString toolTip =
null)
2121 var frame =
new GUIFrame(
new RectTransform(
new Point(parent.Rect.Width, Math.Max(elementHeight, 26)), parent), color: Color.Transparent);
2122 new GUITextBlock(
new RectTransform(
new Vector2(0.4f, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
2126 var inputArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.6f, 1), frame.RectTransform,
Anchor.TopRight), isHorizontal:
true, childAnchor:
Anchor.CenterRight)
2129 RelativeSpacing = 0.05f
2131 for (
int i = 1; i >= 0; i--)
2133 var element =
new GUIFrame(
new RectTransform(
new Vector2(0.45f, 1), inputArea.RectTransform), style:
null);
2134 new GUITextBlock(
new RectTransform(
new Vector2(0.3f, 1), element.RectTransform,
Anchor.CenterLeft), VectorComponentLabels[i], font: GUIStyle.SmallFont, textAlignment: Alignment.CenterLeft);
2135 GUINumberInput numberInput =
new GUINumberInput(
new RectTransform(
new Vector2(0.7f, 1), element.RectTransform,
Anchor.CenterRight),
2138 Font = GUIStyle.SmallFont
2142 numberInput.IntValue = value.X;
2144 numberInput.IntValue = value.Y;
2149 public static GUIComponent CreateVector2Field(Vector2 value,
int elementHeight, LocalizedString name, RectTransform parent, LocalizedString toolTip =
null, GUIFont font =
null,
int decimalsToDisplay = 1)
2151 font = font ?? GUIStyle.SmallFont;
2152 var frame =
new GUIFrame(
new RectTransform(
new Point(parent.Rect.Width, Math.Max(elementHeight, 26)), parent), color: Color.Transparent);
2153 new GUITextBlock(
new RectTransform(
new Vector2(0.4f, 1), frame.RectTransform), name, font: font)
2157 var inputArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.6f, 1), frame.RectTransform,
Anchor.TopRight), isHorizontal:
true, childAnchor:
Anchor.CenterRight)
2160 RelativeSpacing = 0.05f
2162 for (
int i = 1; i >= 0; i--)
2164 var element =
new GUIFrame(
new RectTransform(
new Vector2(0.45f, 1), inputArea.RectTransform), style:
null);
2165 new GUITextBlock(
new RectTransform(
new Vector2(0.3f, 1), element.RectTransform,
Anchor.CenterLeft), VectorComponentLabels[i], font: font, textAlignment: Alignment.CenterLeft);
2166 GUINumberInput numberInput =
new GUINumberInput(
new RectTransform(
new Vector2(0.7f, 1), element.RectTransform,
Anchor.CenterRight),
NumberType.Float) { Font = font };
2170 numberInput.FloatValue = value.X;
2173 numberInput.FloatValue = value.Y;
2176 numberInput.DecimalsToDisplay = decimalsToDisplay;
2181 public static GUITextBox CreateTextBoxWithPlaceholder(RectTransform rectT,
string text, LocalizedString placeholder)
2183 var holder =
new GUIFrame(rectT, style:
null);
2184 var textBox =
new GUITextBox(
new RectTransform(Vector2.One, holder.RectTransform,
Anchor.CenterLeft), text, createClearButton:
false);
2185 var placeholderElement =
new GUITextBlock(
new RectTransform(Vector2.One, holder.RectTransform,
Anchor.CenterLeft),
2186 textColor: Color.DarkGray * 0.6f,
2188 textAlignment: Alignment.CenterLeft)
2190 CanBeFocused =
false
2193 new GUICustomComponent(
new RectTransform(Vector2.Zero, holder.RectTransform),
2194 onUpdate: delegate { placeholderElement.RectTransform.NonScaledSize = textBox.Frame.RectTransform.NonScaledSize; });
2196 textBox.OnSelected += delegate { placeholderElement.Visible =
false; };
2197 textBox.OnDeselected += delegate { placeholderElement.Visible = textBox.Text.IsNullOrWhiteSpace(); };
2199 placeholderElement.Visible =
string.IsNullOrWhiteSpace(text);
2203 public static void NotifyPrompt(LocalizedString header, LocalizedString body)
2205 GUIMessageBox msgBox =
new GUIMessageBox(header, body,
new[] { TextManager.Get(
"Ok") },
new Vector2(0.2f, 0.175f), minSize:
new Point(300, 175));
2206 msgBox.Buttons[0].OnClicked = delegate
2213 public static GUIMessageBox AskForConfirmation(LocalizedString header, LocalizedString body, Action onConfirm, Action onDeny =
null, Vector2? relativeSize =
null, Point? minSize =
null)
2215 LocalizedString[] buttons = { TextManager.Get(
"Ok"), TextManager.Get(
"Cancel") };
2216 GUIMessageBox msgBox =
new GUIMessageBox(header, body, buttons, relativeSize: relativeSize ??
new Vector2(0.2f, 0.175f), minSize: minSize ??
new Point(300, 175));
2219 msgBox.Buttons[1].OnClicked = delegate
2227 msgBox.Buttons[0].OnClicked = delegate
2236 public static GUIMessageBox PromptTextInput(LocalizedString header,
string body, Action<string> onConfirm)
2238 LocalizedString[] buttons = { TextManager.Get(
"Ok"), TextManager.Get(
"Cancel") };
2239 GUIMessageBox msgBox =
new GUIMessageBox(header,
string.Empty, buttons,
new Vector2(0.2f, 0.175f), minSize:
new Point(300, 175));
2240 GUITextBox textBox =
new GUITextBox(
new RectTransform(Vector2.One, msgBox.Content.RectTransform), text: body)
2246 msgBox.Buttons[1].OnClicked = delegate
2253 msgBox.Buttons[0].OnClicked = delegate
2255 onConfirm.Invoke(textBox.Text);
2264 #region Element positioning
2265 private static List<T> CreateElements<T>(
int count, RectTransform parent, Func<RectTransform, T> constructor,
2266 Vector2? relativeSize =
null, Point? absoluteSize =
null,
2267 Anchor anchor =
Anchor.TopLeft,
Pivot? pivot =
null, Point? minSize =
null, Point? maxSize =
null,
2268 int absoluteSpacing = 0,
float relativeSpacing = 0, Func<int, int> extraSpacing =
null,
2269 int startOffsetAbsolute = 0,
float startOffsetRelative = 0,
bool isHorizontal =
false)
2270 where T : GUIComponent
2272 var elements =
new List<T>();
2274 for (
int i = 0; i < count; i++)
2276 if (extraSpacing !=
null)
2278 extraTotal += extraSpacing(i);
2280 if (relativeSize.HasValue)
2282 var size = relativeSize.Value;
2283 var offsets = CalculateOffsets(size, startOffsetRelative, startOffsetAbsolute, relativeSpacing, absoluteSpacing, i, extraTotal, isHorizontal);
2284 elements.Add(constructor(
new RectTransform(size, parent, anchor, pivot, minSize, maxSize)
2286 RelativeOffset = offsets.Item1,
2287 AbsoluteOffset = offsets.Item2
2292 var size = absoluteSize.Value;
2293 var offsets = CalculateOffsets(size, startOffsetRelative, startOffsetAbsolute, relativeSpacing, absoluteSpacing, i, extraTotal, isHorizontal);
2294 elements.Add(constructor(
new RectTransform(size, parent, anchor, pivot)
2296 RelativeOffset = offsets.Item1,
2297 AbsoluteOffset = offsets.Item2
2304 private static Tuple<Vector2, Point> CalculateOffsets(Vector2 relativeSize,
float startOffsetRelative,
int startOffsetAbsolute,
float relativeSpacing,
int absoluteSpacing,
int counter,
int extra,
bool isHorizontal)
2306 float relX = 0, relY = 0;
2307 int absX = 0, absY = 0;
2310 relX = CalculateRelativeOffset(startOffsetRelative, relativeSpacing, relativeSize.X, counter);
2311 absX = CalculateAbsoluteOffset(startOffsetAbsolute, absoluteSpacing, counter, extra);
2315 relY = CalculateRelativeOffset(startOffsetRelative, relativeSpacing, relativeSize.Y, counter);
2316 absY = CalculateAbsoluteOffset(startOffsetAbsolute, absoluteSpacing, counter, extra);
2318 return Tuple.Create(
new Vector2(relX, relY),
new Point(absX, absY));
2321 private static Tuple<Vector2, Point> CalculateOffsets(Point absoluteSize,
float startOffsetRelative,
int startOffsetAbsolute,
float relativeSpacing,
int absoluteSpacing,
int counter,
int extra,
bool isHorizontal)
2323 float relX = 0, relY = 0;
2324 int absX = 0, absY = 0;
2327 relX = CalculateRelativeOffset(startOffsetRelative, relativeSpacing, counter);
2328 absX = CalculateAbsoluteOffset(startOffsetAbsolute, absoluteSpacing, absoluteSize.X, counter, extra);
2332 relY = CalculateRelativeOffset(startOffsetRelative, relativeSpacing, counter);
2333 absY = CalculateAbsoluteOffset(startOffsetAbsolute, absoluteSpacing, absoluteSize.Y, counter, extra);
2335 return Tuple.Create(
new Vector2(relX, relY),
new Point(absX, absY));
2338 private static float CalculateRelativeOffset(
float startOffset,
float spacing,
float size,
int counter)
2340 return startOffset + (spacing + size) * counter;
2343 private static float CalculateRelativeOffset(
float startOffset,
float spacing,
int counter)
2345 return startOffset + spacing * counter;
2348 private static int CalculateAbsoluteOffset(
int startOffset,
int spacing,
int counter,
int extra)
2350 return startOffset + spacing * counter + extra;
2353 private static int CalculateAbsoluteOffset(
int startOffset,
int spacing,
int size,
int counter,
int extra)
2355 return startOffset + (spacing + size) * counter + extra;
2364 public static void PreventElementOverlap(IList<GUIComponent> elements, IList<Rectangle> disallowedAreas =
null, Rectangle? clampArea =
null)
2366 List<GUIComponent> sortedElements = elements.OrderByDescending(e => e.Rect.Width + e.Rect.Height).ToList();
2368 Rectangle area = clampArea ??
new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
2369 for (
int i = 0; i < sortedElements.Count; i++)
2371 Point moveAmount = Point.Zero;
2372 Rectangle rect1 = sortedElements[i].Rect;
2373 moveAmount.X += Math.Max(area.X - rect1.X, 0);
2374 moveAmount.X -= Math.Max(rect1.Right - area.Right, 0);
2375 moveAmount.Y += Math.Max(area.Y - rect1.Y, 0);
2376 moveAmount.Y -= Math.Max(rect1.Bottom - area.Bottom, 0);
2377 sortedElements[i].RectTransform.ScreenSpaceOffset += moveAmount;
2380 bool intersections =
true;
2382 while (intersections && iterations < 100)
2384 intersections =
false;
2385 for (
int i = 0; i < sortedElements.Count; i++)
2387 Rectangle rect1 = sortedElements[i].Rect;
2388 for (
int j = i + 1; j < sortedElements.Count; j++)
2390 Rectangle rect2 = sortedElements[j].Rect;
2391 if (!rect1.Intersects(rect2)) {
continue; }
2393 intersections =
true;
2394 Point centerDiff = rect1.Center - rect2.Center;
2396 Vector2 moveAmount = centerDiff == Point.Zero ? Vector2.UnitX + Rand.Vector(0.1f) : Vector2.Normalize(centerDiff.ToVector2());
2400 if (Math.Abs(moveAmount.X) > Math.Abs(moveAmount.Y) * 8.0f)
2402 moveAmount.Y = 0.0f;
2405 else if (Math.Abs(moveAmount.Y) > Math.Abs(moveAmount.X) * 8.0f)
2407 moveAmount.X = 0.0f;
2411 Vector2 moveAmount1 = ClampMoveAmount(rect1, area, moveAmount * 10.0f);
2412 Vector2 moveAmount2 = ClampMoveAmount(rect2, area, -moveAmount * 10.0f);
2416 sortedElements[i].RectTransform.ScreenSpaceOffset += moveAmount1.ToPoint();
2417 sortedElements[j].RectTransform.ScreenSpaceOffset += moveAmount2.ToPoint();
2420 if (disallowedAreas ==
null) {
continue; }
2421 foreach (Rectangle rect2
in disallowedAreas)
2423 if (!rect1.Intersects(rect2)) {
continue; }
2424 intersections =
true;
2426 Point centerDiff = rect1.Center - rect2.Center;
2428 Vector2 moveAmount = centerDiff == Point.Zero ? Rand.Vector(1.0f) : Vector2.Normalize(centerDiff.ToVector2());
2431 Vector2 moveAmount1 = ClampMoveAmount(rect1, area, moveAmount * 10.0f);
2435 sortedElements[i].RectTransform.ScreenSpaceOffset += (moveAmount1).ToPoint();
2442 private static Vector2 ClampMoveAmount(Rectangle rect, Rectangle clampTo, Vector2 moveAmount)
2444 if (rect.Y < clampTo.Y)
2446 moveAmount.Y = Math.Max(moveAmount.Y, 0.0f);
2448 else if (rect.Bottom > clampTo.Bottom)
2450 moveAmount.Y = Math.Min(moveAmount.Y, 0.0f);
2452 if (rect.X < clampTo.X)
2454 moveAmount.X = Math.Max(moveAmount.X, 0.0f);
2456 else if (rect.Right > clampTo.Right)
2458 moveAmount.X = Math.Min(moveAmount.X, 0.0f);
2467 public static void TogglePauseMenu()
2469 if (Screen.Selected == GameMain.MainMenuScreen) {
return; }
2470 if (PreventPauseMenuToggle) {
return; }
2472 SettingsMenuOpen =
false;
2474 TogglePauseMenu(
null,
null);
2478 Inventory.DraggingItems.Clear();
2479 Inventory.DraggingInventory =
null;
2481 PauseMenu =
new GUIFrame(
new RectTransform(Vector2.One, Canvas,
Anchor.Center), style:
null);
2482 new GUIFrame(
new RectTransform(GUI.Canvas.RelativeSize, PauseMenu.RectTransform,
Anchor.Center), style:
"GUIBackgroundBlocker");
2484 var pauseMenuInner =
new GUIFrame(
new RectTransform(
new Vector2(0.13f, 0.3f), PauseMenu.RectTransform,
Anchor.Center) { MinSize = new Point(250, 300) });
2486 float padding = 0.06f;
2488 var buttonContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.7f, 0.8f), pauseMenuInner.RectTransform,
Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, padding) })
2490 AbsoluteSpacing = IntScale(15)
2493 new GUIButton(
new RectTransform(
new Vector2(0.1f, 0.07f), pauseMenuInner.RectTransform,
Anchor.TopRight) { RelativeOffset = new Vector2(padding) },
2494 "", style:
"GUIBugButton")
2496 IgnoreLayoutGroups =
true,
2497 ToolTip = TextManager.Get(
"bugreportbutton") + $
" (v{GameMain.Version})",
2498 OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter();
return true; }
2501 CreateButton(
"PauseMenuResume", buttonContainer,
null);
2502 CreateButton(
"PauseMenuSettings", buttonContainer, () => SettingsMenuOpen =
true);
2504 bool IsFriendlyOutpostLevel() => GameMain.GameSession !=
null && Level.IsLoadedFriendlyOutpost;
2505 if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession !=
null)
2507 if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode)
2509 CreateButton(
"PauseMenuRetry", buttonContainer, verificationTextTag:
"PauseMenuRetryVerification", action: () =>
2511 if (GameMain.GameSession.RoundSummary?.Frame !=
null)
2513 GUIMessageBox.MessageBoxes.Remove(GameMain.GameSession.RoundSummary.Frame);
2515 GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as
string ==
"ConversationAction");
2516 GameMain.GameSession.LoadPreviousSave();
2519 if (IsFriendlyOutpostLevel() && !spMode.CrewDead)
2521 CreateButton(
"PauseMenuSaveQuit", buttonContainer, verificationTextTag:
"PauseMenuSaveAndReturnToMainMenuVerification", action: () =>
2523 if (IsFriendlyOutpostLevel()) { GameMain.QuitToMainMenu(save:
true); }
2527 else if (GameMain.GameSession.GameMode is TestGameMode)
2529 CreateButton(
"PauseMenuReturnToEditor", buttonContainer, action: () =>
2531 GameMain.GameSession?.EndRound(
"");
2534 else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client !=
null && GameMain.Client.HasPermission(
ClientPermissions.ManageRound))
2536 bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
2539 CreateButton(
"PauseMenuSaveQuit", buttonContainer, verificationTextTag:
"PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
2541 GameMain.Client?.RequestRoundEnd(save:
true);
2545 CreateButton(GameMain.GameSession.GameMode is CampaignMode ?
"ReturnToServerlobby" :
"EndRound", buttonContainer,
2546 verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ?
"PauseMenuReturnToServerLobbyVerification" :
"EndRoundSubNotAtLevelEnd",
2549 GameMain.Client?.RequestRoundEnd(save: false);
2556 CreateButton(
"PauseMenuQuit", buttonContainer,
2557 verificationTextTag: GameMain.GameSession ==
null ?
"PauseMenuQuitVerificationEditor" :
"PauseMenuQuitVerification",
2560 GameMain.QuitToMainMenu(save: false);
2565 CreateButton(
"PauseMenuQuit", buttonContainer, action: () => { GameMain.QuitToMainMenu(save:
false); });
2568 GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock));
2570 pauseMenuInner.RectTransform.MinSize =
new Point(
2571 pauseMenuInner.RectTransform.MinSize.X,
2573 (
int)(buttonContainer.Children.Sum(c => c.Rect.Height + buttonContainer.AbsoluteSpacing) / buttonContainer.RectTransform.RelativeSize.Y),
2574 pauseMenuInner.RectTransform.MinSize.X));
2578 void CreateButton(
string textTag, GUIComponent parent, Action action,
string verificationTextTag =
null)
2580 new GUIButton(
new RectTransform(
new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
2582 OnClicked = (btn, userData) =>
2584 if (
string.IsNullOrEmpty(verificationTextTag))
2586 PauseMenuOpen =
false;
2591 CreateVerificationPrompt(verificationTextTag, action);
2598 void CreateVerificationPrompt(
string textTag, Action confirmAction)
2600 var msgBox =
new GUIMessageBox(
"", TextManager.Get(textTag),
2601 new LocalizedString[] { TextManager.Get(
"Yes"), TextManager.Get(
"No") })
2603 UserData =
"verificationprompt",
2606 msgBox.Buttons[0].OnClicked = (_, __) =>
2608 PauseMenuOpen =
false;
2609 confirmAction?.Invoke();
2612 msgBox.Buttons[0].OnClicked += msgBox.Close;
2613 msgBox.Buttons[1].OnClicked += msgBox.Close;
2617 private static bool TogglePauseMenu(GUIButton button,
object obj)
2619 PauseMenuOpen = !PauseMenuOpen;
2620 if (!PauseMenuOpen && PauseMenu !=
null)
2622 PauseMenu.RectTransform.Parent =
null;
2632 public static void AddMessage(LocalizedString message, Color color,
float? lifeTime =
null,
bool playSound =
true, GUIFont font =
null)
2634 AddMessage(message.Value, color, lifeTime, playSound, font);
2637 public static void AddMessage(LocalizedString message, Color color, Vector2 pos, Vector2 velocity,
float lifeTime = 3.0f,
bool playSound =
true,
GUISoundType soundType =
GUISoundType.UIMessage,
int subId = -1)
2639 AddMessage(message.Value, color, pos, velocity, lifeTime, playSound, soundType, subId);
2642 public static void AddMessage(
string message, Color color,
float? lifeTime =
null,
bool playSound =
true, GUIFont font =
null)
2644 var guiMessage =
new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? GUIStyle.LargeFont);
2647 if (messages.Any(msg => msg.Text == message)) {
return; }
2648 messages.Add(guiMessage);
2650 if (playSound) { SoundPlayer.PlayUISound(
GUISoundType.UIMessage); }
2653 public static void AddMessage(
string message, Color color, Vector2 pos, Vector2 velocity,
float lifeTime = 3.0f,
bool playSound =
true,
GUISoundType soundType =
GUISoundType.UIMessage,
int subId = -1)
2657 var newMessage =
new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, GUIStyle.Font, sub: sub);
2658 if (playSound) { SoundPlayer.PlayUISound(soundType); }
2662 bool overlapFound =
true;
2664 while (overlapFound)
2666 overlapFound =
false;
2667 foreach (var otherMessage
in messages)
2669 float xDiff = otherMessage.Pos.X - newMessage.Pos.X;
2670 if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) {
continue; }
2671 float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y;
2672 if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) {
continue; }
2673 Vector2 moveDir = -(
new Vector2(xDiff, yDiff) + Rand.Vector(1.0f));
2674 if (moveDir.LengthSquared() > 0.0001f)
2676 moveDir = Vector2.Normalize(moveDir);
2680 moveDir = Rand.Vector(1.0f);
2682 moveDir.Y = -Math.Abs(moveDir.Y);
2683 newMessage.Pos -= Vector2.UnitY * 10;
2686 if (tries > 20) {
break; }
2688 messages.Add(newMessage);
2692 public static void ClearMessages()
2700 public static bool IsFourByThree()
2702 float aspectRatio = HorizontalAspectRatio;
2703 return aspectRatio > 1.3f && aspectRatio < 1.4f;
2706 public static void SetSavingIndicatorState(
bool enabled)
2710 timeUntilSavingIndicatorDisabled =
null;
2712 isSavingIndicatorEnabled = enabled;
2715 public static void DisableSavingIndicatorDelayed(
float delay = 3.0f)
2717 if (!isSavingIndicatorEnabled) {
return; }
2718 timeUntilSavingIndicatorDisabled = delay;
CursorState GetMouseCursorState()
static Wire DraggingConnected
static Wire HighlightedWire
static readonly Dictionary< int, GridInfo > Grids
bool FadingOutAndDisposing
static ? SocialOverlay Instance
void AddToGuiUpdateList()
@ Character
Characters only