Add quick interactions locally
This commit is contained in:
115
Quick Interactions/CSharp/Client/CrabUI/Components/CUIButton.cs
Normal file
115
Quick Interactions/CSharp/Client/CrabUI/Components/CUIButton.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// A button
|
||||
/// It's derived from CUITextBlock and has all its props
|
||||
/// </summary>
|
||||
public class CUIButton : CUITextBlock
|
||||
{
|
||||
[CUISerializable]
|
||||
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
|
||||
[CUISerializable] public Color DisabledColor { get; set; }
|
||||
[CUISerializable] public Color InactiveColor { get; set; }
|
||||
[CUISerializable] public Color MouseOverColor { get; set; }
|
||||
[CUISerializable] public Color MousePressedColor { get; set; }
|
||||
[CUISerializable] public bool AutoUpdateColor { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Convenient prop to set all colors at once
|
||||
/// </summary>
|
||||
public Color MasterColor
|
||||
{
|
||||
set
|
||||
{
|
||||
InactiveColor = value.Multiply(0.7f);
|
||||
MouseOverColor = value.Multiply(0.9f);
|
||||
MousePressedColor = value;
|
||||
DetermineColor();
|
||||
}
|
||||
}
|
||||
|
||||
public Color MasterColorOpaque
|
||||
{
|
||||
set
|
||||
{
|
||||
InactiveColor = new Color((int)(value.R * 0.7f), (int)(value.G * 0.7f), (int)(value.B * 0.7f), value.A);
|
||||
MouseOverColor = new Color((int)(value.R * 0.9f), (int)(value.G * 0.9f), (int)(value.B * 0.9f), value.A);
|
||||
MousePressedColor = value;
|
||||
DetermineColor();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BackgroundColor is used in base.Draw, but here it's calculated from colors above
|
||||
/// So it's not a prop anymore, and i don't want to serialize it
|
||||
/// </summary>
|
||||
public new Color BackgroundColor
|
||||
{
|
||||
get => CUIProps.BackgroundColor.Value;
|
||||
set => CUIProps.BackgroundColor.SetValue(value);
|
||||
}
|
||||
|
||||
public void DetermineColor()
|
||||
{
|
||||
if (!AutoUpdateColor) return;
|
||||
if (Disabled)
|
||||
{
|
||||
BackgroundColor = DisabledColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
BackgroundColor = InactiveColor;
|
||||
if (MouseOver) BackgroundColor = MouseOverColor;
|
||||
if (MousePressed) BackgroundColor = MousePressedColor;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
//DetermineColor();
|
||||
base.Draw(spriteBatch);
|
||||
}
|
||||
public CUIButton() : base()
|
||||
{
|
||||
Text = "CUIButton";
|
||||
ConsumeMouseClicks = true;
|
||||
ConsumeDragAndDrop = true;
|
||||
ConsumeSwipe = true;
|
||||
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
if (!Disabled)
|
||||
{
|
||||
SoundPlayer.PlayUISound(ClickSound);
|
||||
if (Command != null && Command != "")
|
||||
{
|
||||
DispatchUp(new CUICommand(Command));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OnMouseOff += (e) => DetermineColor();
|
||||
OnMouseOn += (e) => DetermineColor();
|
||||
OnStyleApplied += DetermineColor;
|
||||
DetermineColor();
|
||||
}
|
||||
|
||||
public CUIButton(string text) : this()
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
105
Quick Interactions/CSharp/Client/CrabUI/Components/CUICanvas.cs
Normal file
105
Quick Interactions/CSharp/Client/CrabUI/Components/CUICanvas.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to manipulate pixel data of its texture
|
||||
/// </summary>
|
||||
public class CUICanvas : CUIComponent, IDisposable
|
||||
{
|
||||
public Color[] Data;
|
||||
|
||||
public RenderTarget2D Texture;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Size of the internal texture
|
||||
/// Will automatically resize the texture and data array of set
|
||||
/// </summary>
|
||||
public virtual Point Size
|
||||
{
|
||||
get => new Point(Texture.Width, Texture.Height);
|
||||
set
|
||||
{
|
||||
if (value.X == Texture?.Width && value.Y == Texture?.Height) return;
|
||||
|
||||
RenderTarget2D oldTexture = Texture;
|
||||
Texture = new RenderTarget2D(GameMain.Instance.GraphicsDevice, value.X, value.Y);
|
||||
Data = new Color[Texture.Width * Texture.Height];
|
||||
BackgroundSprite = new CUISprite(Texture);
|
||||
oldTexture?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear(Color? color = null)
|
||||
{
|
||||
Color cl = color ?? Color.Transparent;
|
||||
for (int i = 0; i < Data.Length; i++)
|
||||
{
|
||||
Data[i] = cl;
|
||||
}
|
||||
|
||||
SetData();
|
||||
}
|
||||
|
||||
public Color GetPixel(int x, int y)
|
||||
{
|
||||
return Data[y * Texture.Width + x];
|
||||
}
|
||||
|
||||
public void SetPixel(int x, int y, Color cl)
|
||||
{
|
||||
Data[y * Texture.Width + x] = cl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this method to transfer values from Data array into texture
|
||||
/// </summary>
|
||||
public void SetData()
|
||||
{
|
||||
Texture.SetData<Color>(Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses renderFunc to render stuff directy onto Canvas.Texture
|
||||
/// You can for example use GUI "Draw" methods with provided spriteBatch
|
||||
/// </summary>
|
||||
/// <param name="renderFunc"> Action<SpriteBatch> where you can draw whatever you want </param>
|
||||
public void Render(Action<SpriteBatch> renderFunc)
|
||||
{
|
||||
GameMain.Instance.GraphicsDevice.SetRenderTarget(Texture);
|
||||
|
||||
//TODO save and restore scissor rect
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
|
||||
|
||||
renderFunc(spriteBatch);
|
||||
|
||||
spriteBatch.End();
|
||||
|
||||
GameMain.Instance.GraphicsDevice.SetRenderTarget(null);
|
||||
}
|
||||
|
||||
public SpriteBatch spriteBatch;
|
||||
|
||||
public CUICanvas(int x, int y) : base()
|
||||
{
|
||||
Size = new Point(x, y);
|
||||
spriteBatch = new SpriteBatch(GameMain.Instance.GraphicsDevice);
|
||||
}
|
||||
|
||||
public CUICanvas() : this(100, 100) { }
|
||||
|
||||
public override void CleanUp()
|
||||
{
|
||||
Texture?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent : IDisposable
|
||||
{
|
||||
private void SetupAnimations()
|
||||
{
|
||||
Animations = new Indexer<string, CUIAnimation>(
|
||||
(key) => animations.GetValueOrDefault(key),
|
||||
(key, value) => AddAnimation(key, value)
|
||||
);
|
||||
}
|
||||
private Dictionary<string, CUIAnimation> animations = new();
|
||||
public Indexer<string, CUIAnimation> Animations;
|
||||
public void AddAnimation(string name, CUIAnimation animation)
|
||||
{
|
||||
animation.Target = this;
|
||||
animations[name] = animation;
|
||||
}
|
||||
|
||||
public void BlockChildrenAnimations()
|
||||
{
|
||||
foreach (CUIComponent child in Children)
|
||||
{
|
||||
foreach (CUIAnimation animation in child.animations.Values)
|
||||
{
|
||||
animation.Stop();
|
||||
animation.Blocked = true;
|
||||
}
|
||||
child.BlockChildrenAnimations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Just a wrapper for CUIProps
|
||||
/// idk how to separate them better
|
||||
/// </summary>
|
||||
//TODO this should be a dict, and cuiprop should have hash
|
||||
public CUIComponentProps CUIProps { get; set; } = new();
|
||||
|
||||
|
||||
public class CUIComponentProps
|
||||
{
|
||||
public CUIProp<int?> ZIndex = new CUIProp<int?>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
foreach (var child in host.Children)
|
||||
{
|
||||
//HACK think, should i propagate null?
|
||||
if (v.HasValue && !child.IgnoreParentZIndex)
|
||||
{
|
||||
child.ZIndex = v.Value + 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<bool> IgnoreEvents = new CUIProp<bool>()
|
||||
{
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
foreach (var child in host.Children)
|
||||
{
|
||||
if (!child.IgnoreParentEventIgnorance) child.IgnoreEvents = v;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<bool> Visible = new CUIProp<bool>()
|
||||
{
|
||||
Value = true,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
foreach (var child in host.Children)
|
||||
{
|
||||
if (!child.IgnoreParentVisibility) child.Visible = v;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<bool> Revealed = new CUIProp<bool>()
|
||||
{
|
||||
Value = true,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
// host.TreeChanged = true;
|
||||
host.Visible = v;
|
||||
host.IgnoreEvents = !v;
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<CUIBool2> Ghost = new CUIProp<CUIBool2>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
AbsoluteProp = true,
|
||||
};
|
||||
|
||||
public CUIProp<bool> CullChildren = new CUIProp<bool>()
|
||||
{
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
host.HideChildrenOutsideFrame = v;
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<CUI3DOffset> ChildrenOffset = new CUIProp<CUI3DOffset>()
|
||||
{
|
||||
ChildProp = true,
|
||||
Value = new CUI3DOffset(0, 0, 1), // uuuuuuuuu suka blyat!
|
||||
Validate = (v, host) => host.ChildOffsetBounds.Check(v),
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
foreach (var child in host.Children)
|
||||
{
|
||||
if (!child.Fixed) child.Scale = v.Z;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<bool> ResizeToSprite = new CUIProp<bool>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
if (v)
|
||||
{
|
||||
host.Absolute = host.Absolute with
|
||||
{
|
||||
Width = host.BackgroundSprite.SourceRect.Width,
|
||||
Height = host.BackgroundSprite.SourceRect.Height,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
public CUIProp<CUIBool2> FillEmptySpace = new CUIProp<CUIBool2>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
};
|
||||
|
||||
public CUIProp<CUIBool2> FitContent = new CUIProp<CUIBool2>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
AbsoluteProp = true,
|
||||
};
|
||||
|
||||
public CUIProp<CUINullRect> Absolute = new CUIProp<CUINullRect>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
AbsoluteProp = true,
|
||||
};
|
||||
|
||||
public CUIProp<CUINullRect> AbsoluteMin = new CUIProp<CUINullRect>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
AbsoluteProp = true,
|
||||
};
|
||||
|
||||
public CUIProp<CUINullRect> AbsoluteMax = new CUIProp<CUINullRect>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
AbsoluteProp = true,
|
||||
};
|
||||
|
||||
public CUIProp<CUINullRect> Relative = new CUIProp<CUINullRect>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
};
|
||||
public CUIProp<CUINullRect> RelativeMin = new CUIProp<CUINullRect>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
};
|
||||
public CUIProp<CUINullRect> RelativeMax = new CUIProp<CUINullRect>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
};
|
||||
public CUIProp<CUINullRect> CrossRelative = new CUIProp<CUINullRect>()
|
||||
{
|
||||
LayoutProp = true,
|
||||
};
|
||||
|
||||
#region Graphic Props --------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
|
||||
public CUIProp<PaletteOrder> Palette = new CUIProp<PaletteOrder>()
|
||||
{
|
||||
ShowInDebug = false,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
//TODO should this be called in deserialize?
|
||||
CUIGlobalStyleResolver.OnComponentStyleChanged(host);
|
||||
// foreach (var child in host.Children)
|
||||
// {
|
||||
// child.Palette = v;
|
||||
// }
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<CUISprite> BackgroundSprite = new CUIProp<CUISprite>()
|
||||
{
|
||||
Value = CUISprite.Default,
|
||||
ShowInDebug = false,
|
||||
Validate = (v, host) => v ?? CUISprite.Default,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
if (host.ResizeToSprite)
|
||||
{
|
||||
host.Absolute = host.Absolute with
|
||||
{
|
||||
Width = v.SourceRect.Width,
|
||||
Height = v.SourceRect.Height,
|
||||
};
|
||||
}
|
||||
|
||||
if (host.IgnoreTransparent)
|
||||
{
|
||||
Rectangle bounds = host.BackgroundSprite.Texture.Bounds;
|
||||
host.TextureData = new Color[bounds.Width * bounds.Height];
|
||||
host.BackgroundSprite.Texture.GetData<Color>(host.TextureData);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<bool> IgnoreTransparent = new CUIProp<bool>()
|
||||
{
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
if (v)
|
||||
{
|
||||
Rectangle bounds = host.BackgroundSprite.Texture.Bounds;
|
||||
host.TextureData = new Color[bounds.Width * bounds.Height];
|
||||
host.BackgroundSprite.Texture.GetData<Color>(host.TextureData);
|
||||
}
|
||||
else
|
||||
{
|
||||
host.TextureData = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<Color> BackgroundColor = new CUIProp<Color>()
|
||||
{
|
||||
ShowInDebug = false,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
host.BackgroundVisible = v != Color.Transparent;
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<Color> OutlineColor = new CUIProp<Color>()
|
||||
{
|
||||
ShowInDebug = false,
|
||||
OnSet = (v, host) =>
|
||||
{
|
||||
host.OutlineVisible = v != Color.Transparent;
|
||||
},
|
||||
};
|
||||
|
||||
public CUIProp<Vector2> Padding = new CUIProp<Vector2>()
|
||||
{
|
||||
Value = new Vector2(2, 2),
|
||||
DecorProp = true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Global ID, unique for component
|
||||
/// </summary>
|
||||
public int ID { get; set; }
|
||||
|
||||
internal bool DebugHighlight { get; set; }
|
||||
|
||||
private CUIMainComponent mainComponent;
|
||||
/// <summary>
|
||||
/// Link to CUIMainComponent, passed to children
|
||||
/// </summary>
|
||||
public CUIMainComponent MainComponent
|
||||
{
|
||||
get => mainComponent;
|
||||
set
|
||||
{
|
||||
mainComponent = value;
|
||||
foreach (var child in Children) { child.MainComponent = value; }
|
||||
}
|
||||
}
|
||||
|
||||
internal int positionalZIndex;
|
||||
internal int addedZIndex;
|
||||
|
||||
[Calculated] public bool Focused { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True when parent has HideChildrenOutsideFrame and child wanders beyond parents border
|
||||
/// </summary>
|
||||
[Calculated] internal bool CulledOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// BackgroundColor != Color.Transparent
|
||||
/// </summary>
|
||||
protected bool BackgroundVisible { get; set; }
|
||||
|
||||
protected bool OutlineVisible { get; set; }
|
||||
|
||||
// This is for state clones, to protect them from style changes
|
||||
internal bool Unreal { get; set; }
|
||||
|
||||
public bool MouseOver { get; set; }
|
||||
public bool MousePressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is used by text to prevent resizing beyond that
|
||||
/// and works as AbsoluteMin
|
||||
/// </summary>
|
||||
[Calculated]
|
||||
public CUINullVector2 ForcedMinSize
|
||||
{
|
||||
get => forsedSize;
|
||||
set => SetForcedMinSize(value);
|
||||
}
|
||||
protected CUINullVector2 forsedSize; internal void SetForcedMinSize(CUINullVector2 value, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
forsedSize = value;
|
||||
CUIDebug.Capture(null, this, "SetForcedMinSize", memberName, "ForcedMinSize", ForcedMinSize.ToString());
|
||||
OnPropChanged();//TODO this is the reason why lists with a lot of children lag
|
||||
//OnSelfAndParentChanged();
|
||||
OnAbsolutePropChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is set by ChildrenOffset when zooming, and iirc consumed by text to adjust text scale
|
||||
/// </summary>
|
||||
[Calculated]
|
||||
public float Scale
|
||||
{
|
||||
get => scale;
|
||||
set => SetScale(value);
|
||||
}
|
||||
protected float scale = 1f; internal void SetScale(float value, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
scale = value;
|
||||
foreach (var child in Children) { child.Scale = value; }
|
||||
// OnDecorPropChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculated Prop, Real + BorderThickness
|
||||
/// </summary>
|
||||
protected CUIRect BorderBox { get; set; }
|
||||
protected CUIRect OutlineBox { get; set; }
|
||||
internal Rectangle? ScissorRect { get; set; }
|
||||
/// <summary>
|
||||
/// Buffer for texture data, for IgnoreTransparent checks
|
||||
/// </summary>
|
||||
protected Color[] TextureData;
|
||||
/// <summary>
|
||||
/// Calculated prop, position on real screen in pixels
|
||||
/// Should be fully calculated after CUIMainComponent.Update
|
||||
/// </summary>
|
||||
[Calculated]
|
||||
public CUIRect Real
|
||||
{
|
||||
get => real;
|
||||
set => SetReal(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private CUIRect real; internal void SetReal(CUIRect value, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
//HACK idk if i need it
|
||||
real = new CUIRect(
|
||||
(float)Math.Round(value.Left),
|
||||
(float)Math.Round(value.Top),
|
||||
(float)Math.Round(value.Width),
|
||||
(float)Math.Round(value.Height)
|
||||
);
|
||||
// real = value;
|
||||
CUIDebug.Capture(null, this, "SetReal", memberName, "real", real.ToString());
|
||||
|
||||
|
||||
BorderBox = real;
|
||||
// BorderBox = new CUIRect(
|
||||
// real.Left - BorderThickness,
|
||||
// real.Top - BorderThickness,
|
||||
// real.Width + 2 * BorderThickness,
|
||||
// real.Height + 2 * BorderThickness
|
||||
// );
|
||||
|
||||
OutlineBox = new CUIRect(
|
||||
real.Left - OutlineThickness,
|
||||
real.Top - OutlineThickness,
|
||||
real.Width + 2 * OutlineThickness,
|
||||
real.Height + 2 * OutlineThickness
|
||||
);
|
||||
|
||||
if (HideChildrenOutsideFrame)
|
||||
{
|
||||
Rectangle SRect = real.Box;
|
||||
|
||||
// //HACK Remove these + 1
|
||||
// Rectangle SRect = new Rectangle(
|
||||
// (int)real.Left + 1,
|
||||
// (int)real.Top + 1,
|
||||
// (int)real.Width - 2,
|
||||
// (int)real.Height - 2
|
||||
// );
|
||||
|
||||
if (Parent?.ScissorRect != null)
|
||||
{
|
||||
ScissorRect = Rectangle.Intersect(Parent.ScissorRect.Value, SRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScissorRect = SRect;
|
||||
}
|
||||
}
|
||||
else ScissorRect = Parent?.ScissorRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
|
||||
public class CommandAttribute : System.Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Can be dispatched up the component tree to notify parent about something
|
||||
/// add pass some event data without creating a hard link
|
||||
/// </summary>
|
||||
/// <param name="Name"></param>
|
||||
public record CUICommand(string Name, object Data = null);
|
||||
|
||||
/// <summary>
|
||||
/// Can be dispatched down the component tree to pass some data to the children
|
||||
/// without creating a hard link
|
||||
/// </summary>
|
||||
public record CUIData(string Name, object Data = null);
|
||||
public partial class CUIComponent
|
||||
{
|
||||
private void SetupCommands()
|
||||
{
|
||||
// This is actually expensive
|
||||
//AddCommands();
|
||||
OnTreeChanged += UpdateDataTargets;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This command will be dispatched up when some component specific event happens
|
||||
/// </summary>
|
||||
[CUISerializable] public string Command { get; set; }
|
||||
/// <summary>
|
||||
/// this will be executed on any command
|
||||
/// </summary>
|
||||
public event Action<CUICommand> OnAnyCommand;
|
||||
/// <summary>
|
||||
/// Will be executed when receiving any data
|
||||
/// </summary>
|
||||
public event Action<CUIData> OnAnyData;
|
||||
/// <summary>
|
||||
/// Happens when appropriate data is received
|
||||
/// </summary>
|
||||
public event Action<Object> OnConsume;
|
||||
/// <summary>
|
||||
/// Will consume data with this name
|
||||
/// </summary>
|
||||
[CUISerializable] public string Consumes { get; set; }
|
||||
|
||||
private bool reflectCommands;
|
||||
[CUISerializable]
|
||||
public bool ReflectCommands
|
||||
{
|
||||
get => reflectCommands;
|
||||
set
|
||||
{
|
||||
reflectCommands = value;
|
||||
OnAnyCommand += (command) =>
|
||||
{
|
||||
foreach (CUIComponent child in Children)
|
||||
{
|
||||
child.DispatchDown(new CUIData(command.Name, command.Data));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private bool retranslateCommands;
|
||||
[CUISerializable]
|
||||
public bool RetranslateCommands
|
||||
{
|
||||
get => retranslateCommands;
|
||||
set
|
||||
{
|
||||
retranslateCommands = value;
|
||||
OnAnyCommand += (command) =>
|
||||
{
|
||||
Parent?.DispatchUp(command);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimization to data flow
|
||||
/// If not empty component will search for consumers of the data
|
||||
/// and pass it directly to them instead of broadcasting it
|
||||
/// </summary>
|
||||
//[CUISerializable]
|
||||
public ObservableCollection<string> Emits
|
||||
{
|
||||
get => emits;
|
||||
set
|
||||
{
|
||||
emits = value;
|
||||
emits.CollectionChanged += (o, e) => UpdateDataTargets();
|
||||
UpdateDataTargets();
|
||||
}
|
||||
}
|
||||
private ObservableCollection<string> emits = new();
|
||||
|
||||
private void UpdateDataTargets()
|
||||
{
|
||||
if (Emits.Count > 0)
|
||||
{
|
||||
DataTargets.Clear();
|
||||
|
||||
RunRecursiveOn(this, (c) =>
|
||||
{
|
||||
if (Emits.Contains(c.Consumes))
|
||||
{
|
||||
if (!DataTargets.ContainsKey(c.Consumes)) DataTargets[c.Consumes] = new();
|
||||
DataTargets[c.Consumes].Add(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consumers of emmited data, updates on tree change
|
||||
/// </summary>
|
||||
public Dictionary<string, List<CUIComponent>> DataTargets = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// All commands
|
||||
/// </summary>
|
||||
public Dictionary<string, Action<object>> Commands { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Manually adds command
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="action"></param>
|
||||
public void AddCommand(string name, Action<object> action) => Commands.Add(name, action);
|
||||
public void RemoveCommand(string name) => Commands.Remove(name);
|
||||
|
||||
/// <summary>
|
||||
/// Executed autpmatically on component creation
|
||||
/// Methods ending in "Command" will be added as commands
|
||||
/// </summary>
|
||||
private void AddCommands()
|
||||
{
|
||||
foreach (MethodInfo mi in this.GetType().GetMethods())
|
||||
{
|
||||
if (Attribute.IsDefined(mi, typeof(CommandAttribute)))
|
||||
{
|
||||
try
|
||||
{
|
||||
string name = mi.Name;
|
||||
if (name != "Command" && name.EndsWith("Command"))
|
||||
{
|
||||
name = name.Substring(0, name.Length - "Command".Length);
|
||||
}
|
||||
AddCommand(name, mi.CreateDelegate<Action<object>>(this));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Info($"{e.Message}\nMethod: {this.GetType()}.{mi.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispathes command up the component tree until someone consumes it
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
public void DispatchUp(CUICommand command)
|
||||
{
|
||||
if (OnAnyCommand != null) OnAnyCommand?.Invoke(command);
|
||||
else if (Commands.ContainsKey(command.Name)) Execute(command);
|
||||
else Parent?.DispatchUp(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispathes command down the component tree until someone consumes it
|
||||
/// </summary>
|
||||
public void DispatchDown(CUIData data)
|
||||
{
|
||||
if (Emits.Contains(data.Name))
|
||||
{
|
||||
if (DataTargets.ContainsKey(data.Name))
|
||||
{
|
||||
foreach (CUIComponent target in DataTargets[data.Name])
|
||||
{
|
||||
target.OnConsume?.Invoke(data.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Consumes == data.Name) OnConsume?.Invoke(data.Data);
|
||||
else if (OnAnyData != null) OnAnyData.Invoke(data);
|
||||
else
|
||||
{
|
||||
foreach (CUIComponent child in Children) child.DispatchDown(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will execute action corresponding to this command
|
||||
/// </summary>
|
||||
/// <param name="commandName"></param>
|
||||
public void Execute(CUICommand command)
|
||||
{
|
||||
Commands.GetValueOrDefault(command.Name)?.Invoke(command.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
#region Debug --------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Mark component and its children for debug
|
||||
/// Used in debug interface
|
||||
/// </summary>
|
||||
private bool debug; public bool Debug
|
||||
{
|
||||
get => debug;
|
||||
set
|
||||
{
|
||||
debug = value;
|
||||
//foreach (CUIComponent c in Children) { c.Debug = value; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For debug frame itself
|
||||
/// </summary>
|
||||
private bool ignoreDebug; public bool IgnoreDebug
|
||||
{
|
||||
get => ignoreDebug;
|
||||
set
|
||||
{
|
||||
ignoreDebug = value;
|
||||
foreach (CUIComponent c in Children) { c.IgnoreDebug = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public void PrintTree(string offset = "")
|
||||
{
|
||||
CUI.Log($"{offset}{this}");
|
||||
foreach (CUIComponent child in Children)
|
||||
{
|
||||
child.PrintTree(offset + "| ");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints component and then message
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="lineNumber"></param>
|
||||
public void Info(object msg, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
var fi = new FileInfo(source);
|
||||
|
||||
CUI.Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", Color.Yellow * 0.5f);
|
||||
CUI.Log($"{this} {msg ?? "null"}", Color.Yellow);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region AKA --------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Parent can memorize it's children by their names, AKA
|
||||
/// </summary>
|
||||
[CUISerializable] public string AKA { get; set; }
|
||||
/// <summary>
|
||||
/// All memorized components
|
||||
/// </summary>
|
||||
public Dictionary<string, CUIComponent> NamedComponents { get; set; } = new();
|
||||
|
||||
public CUIComponent Remember(CUIComponent c, string name)
|
||||
{
|
||||
NamedComponents[name] = c;
|
||||
c.AKA = name;
|
||||
return c;
|
||||
}
|
||||
/// <summary>
|
||||
/// If it already has AKA
|
||||
/// </summary>
|
||||
public CUIComponent Remember(CUIComponent c)
|
||||
{
|
||||
if (c.AKA != null) NamedComponents[c.AKA] = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
public CUIComponent Forget(string name)
|
||||
{
|
||||
if (name == null) return null;
|
||||
CUIComponent c = NamedComponents.GetValueOrDefault(name);
|
||||
NamedComponents.Remove(name);
|
||||
return c;
|
||||
}
|
||||
/// <summary>
|
||||
/// If it already has AKA
|
||||
/// </summary>
|
||||
public CUIComponent Forget(CUIComponent c)
|
||||
{
|
||||
if (c?.AKA != null) NamedComponents.Remove(c.AKA);
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// You can access NamedComponents with this indexer
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public CUIComponent this[string name]
|
||||
{
|
||||
get => Get(name);
|
||||
set
|
||||
{
|
||||
if (value.Parent != null) Remember(value, name);
|
||||
else Append(value, name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns memorized component by name
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public virtual CUIComponent Get(string name)
|
||||
{
|
||||
if (name == null) return null;
|
||||
if (NamedComponents.ContainsKey(name)) return NamedComponents[name];
|
||||
|
||||
CUIComponent component = this;
|
||||
string[] names = name.Split('.');
|
||||
|
||||
foreach (string n in names)
|
||||
{
|
||||
component = component.NamedComponents.GetValueOrDefault(n);
|
||||
|
||||
if (component == null)
|
||||
{
|
||||
CUI.Warning($"Failed to Get {name} from {this}, there's no {n}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
public T Get<T>(string name) where T : CUIComponent => (T)Get(name);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
#region Events --------------------------------------------------------
|
||||
|
||||
[CUISerializable] public bool ConsumeMouseClicks { get; set; }
|
||||
[CUISerializable] public bool ConsumeDragAndDrop { get; set; }
|
||||
[CUISerializable] public bool ConsumeSwipe { get; set; }
|
||||
[CUISerializable] public bool ConsumeMouseScroll { get; set; }
|
||||
|
||||
//HACK no one will ever find it, hehehe
|
||||
public void CascadeRefresh()
|
||||
{
|
||||
if (this is IRefreshable refreshable) refreshable.Refresh();
|
||||
Children.ForEach(c => c.CascadeRefresh());
|
||||
}
|
||||
|
||||
public event Action OnTreeChanged;
|
||||
public event Action<double> OnUpdate;
|
||||
public event Action<CUIInput> OnMouseLeave;
|
||||
public event Action<CUIInput> OnMouseEnter;
|
||||
public event Action<CUIInput> OnMouseDown;
|
||||
public event Action<CUIInput> OnMouseUp;
|
||||
public event Action<CUIInput> OnMouseMove;
|
||||
public event Action<CUIInput> OnMouseOn;
|
||||
public event Action<CUIInput> OnMouseOff;
|
||||
public event Action<CUIInput> OnClick;
|
||||
public event Action<CUIInput> OnDClick;
|
||||
public event Action<CUIInput> OnScroll;
|
||||
public event Action<float, float> OnDrag;
|
||||
public event Action<float, float> OnSwipe;
|
||||
public event Action<CUIInput> OnKeyDown;
|
||||
public event Action<CUIInput> OnKeyUp;
|
||||
public event Action<CUIInput> OnTextInput;
|
||||
public event Action OnFocus;
|
||||
public event Action OnFocusLost;
|
||||
|
||||
|
||||
public Action<double> AddOnUpdate { set { OnUpdate += value; } }
|
||||
public Action<CUIInput> AddOnMouseLeave { set { OnMouseLeave += value; } }
|
||||
public Action<CUIInput> AddOnMouseEnter { set { OnMouseEnter += value; } }
|
||||
public Action<CUIInput> AddOnMouseDown { set { OnMouseDown += value; } }
|
||||
public Action<CUIInput> AddOnMouseUp { set { OnMouseUp += value; } }
|
||||
public Action<CUIInput> AddOnMouseMove { set { OnMouseMove += value; } }
|
||||
public Action<CUIInput> AddOnMouseOn { set { OnMouseOn += value; } }
|
||||
public Action<CUIInput> AddOnMouseOff { set { OnMouseOff += value; } }
|
||||
public Action<CUIInput> AddOnClick { set { OnClick += value; } }
|
||||
public Action<CUIInput> AddOnDClick { set { OnDClick += value; } }
|
||||
public Action<CUIInput> AddOnScroll { set { OnScroll += value; } }
|
||||
public Action<float, float> AddOnDrag { set { OnDrag += value; } }
|
||||
public Action<float, float> AddOnSwipe { set { OnSwipe += value; } }
|
||||
public Action<CUIInput> AddOnKeyDown { set { OnKeyDown += value; } }
|
||||
public Action<CUIInput> AddOnKeyUp { set { OnKeyUp += value; } }
|
||||
public Action<CUIInput> AddOnTextInput { set { OnTextInput += value; } }
|
||||
public Action AddOnFocus { set { OnFocus += value; } }
|
||||
public Action AddOnFocusLost { set { OnFocusLost += value; } }
|
||||
|
||||
//TODO add more CUISpriteDrawModes
|
||||
public virtual bool IsPointOnTransparentPixel(Vector2 point)
|
||||
{
|
||||
if (BackgroundSprite.DrawMode != CUISpriteDrawMode.Resize) return true;
|
||||
|
||||
//TODO hangle case where offset != sprite.origin
|
||||
Vector2 RotationCenter = new Vector2(
|
||||
BackgroundSprite.Offset.X * Real.Width,
|
||||
BackgroundSprite.Offset.Y * Real.Height
|
||||
);
|
||||
|
||||
Vector2 v = (point - Real.Position - RotationCenter).Rotate(-BackgroundSprite.Rotation) + RotationCenter;
|
||||
|
||||
float x = v.X / Real.Width;
|
||||
float y = v.Y / Real.Height;
|
||||
|
||||
Rectangle bounds = BackgroundSprite.Texture.Bounds;
|
||||
Rectangle SourceRect = BackgroundSprite.SourceRect;
|
||||
|
||||
int textureX = (int)Math.Round(SourceRect.X + x * SourceRect.Width);
|
||||
int textureY = (int)Math.Round(SourceRect.Y + y * SourceRect.Height);
|
||||
|
||||
if (textureX < SourceRect.X || (SourceRect.X + SourceRect.Width - 1) < textureX) return true;
|
||||
if (textureY < SourceRect.Y || (SourceRect.Y + SourceRect.Height - 1) < textureY) return true;
|
||||
|
||||
Color cl = TextureData[textureY * bounds.Width + textureX];
|
||||
|
||||
return cl.A == 0;
|
||||
}
|
||||
|
||||
|
||||
public virtual bool ShouldInvoke(CUIInput e)
|
||||
{
|
||||
if (IgnoreTransparent)
|
||||
{
|
||||
return !IsPointOnTransparentPixel(e.MousePosition);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void InvokeOnUpdate(double totalTime) => OnUpdate?.Invoke(totalTime);
|
||||
internal void InvokeOnMouseLeave(CUIInput e) { OnMouseLeave?.Invoke(e); }
|
||||
internal void InvokeOnMouseEnter(CUIInput e) { if (ShouldInvoke(e)) OnMouseEnter?.Invoke(e); }
|
||||
internal void InvokeOnMouseDown(CUIInput e) { if (ShouldInvoke(e)) OnMouseDown?.Invoke(e); }
|
||||
internal void InvokeOnMouseUp(CUIInput e) { if (ShouldInvoke(e)) OnMouseUp?.Invoke(e); }
|
||||
internal void InvokeOnMouseMove(CUIInput e) { if (ShouldInvoke(e)) OnMouseMove?.Invoke(e); }
|
||||
internal void InvokeOnMouseOn(CUIInput e) { if (ShouldInvoke(e)) OnMouseOn?.Invoke(e); }
|
||||
internal void InvokeOnMouseOff(CUIInput e) { if (ShouldInvoke(e)) OnMouseOff?.Invoke(e); }
|
||||
internal void InvokeOnClick(CUIInput e) { if (ShouldInvoke(e)) OnClick?.Invoke(e); }
|
||||
internal void InvokeOnDClick(CUIInput e) { if (ShouldInvoke(e)) OnDClick?.Invoke(e); }
|
||||
internal void InvokeOnScroll(CUIInput e) { if (ShouldInvoke(e)) OnScroll?.Invoke(e); }
|
||||
internal void InvokeOnDrag(float x, float y) => OnDrag?.Invoke(x, y);
|
||||
internal void InvokeOnSwipe(float x, float y) => OnSwipe?.Invoke(x, y);
|
||||
internal void InvokeOnKeyDown(CUIInput e) { if (ShouldInvoke(e)) OnKeyDown?.Invoke(e); }
|
||||
internal void InvokeOnKeyUp(CUIInput e) { if (ShouldInvoke(e)) OnKeyUp?.Invoke(e); }
|
||||
internal void InvokeOnTextInput(CUIInput e) { if (ShouldInvoke(e)) OnTextInput?.Invoke(e); }
|
||||
internal void InvokeOnFocus() => OnFocus?.Invoke();
|
||||
internal void InvokeOnFocusLost() => OnFocusLost?.Invoke();
|
||||
|
||||
#endregion
|
||||
#region Handles --------------------------------------------------------
|
||||
|
||||
internal CUIDragHandle DragHandle = new CUIDragHandle();
|
||||
[CUISerializable]
|
||||
public bool Draggable
|
||||
{
|
||||
get => DragHandle.Draggable;
|
||||
set => DragHandle.Draggable = value;
|
||||
}
|
||||
//HACK Do i really need this?
|
||||
internal CUIFocusHandle FocusHandle = new CUIFocusHandle();
|
||||
[CUISerializable]
|
||||
public bool Focusable
|
||||
{
|
||||
get => FocusHandle.Focusable;
|
||||
set => FocusHandle.Focusable = value;
|
||||
}
|
||||
public CUIResizeHandle LeftResizeHandle = new CUIResizeHandle(new Vector2(0, 1), new CUIBool2(false, false));
|
||||
public CUIResizeHandle RightResizeHandle = new CUIResizeHandle(new Vector2(1, 1), new CUIBool2(true, false));
|
||||
public bool Resizible
|
||||
{
|
||||
get => ResizibleLeft || ResizibleRight;
|
||||
set { ResizibleLeft = value; ResizibleRight = value; }
|
||||
}
|
||||
|
||||
[CUISerializable]
|
||||
public bool ResizibleLeft
|
||||
{
|
||||
get => LeftResizeHandle.Visible;
|
||||
set => LeftResizeHandle.Visible = value;
|
||||
}
|
||||
|
||||
[CUISerializable]
|
||||
public bool ResizibleRight
|
||||
{
|
||||
get => RightResizeHandle.Visible;
|
||||
set => RightResizeHandle.Visible = value;
|
||||
}
|
||||
|
||||
[CUISerializable]
|
||||
public CUIBool2 ResizeDirection
|
||||
{
|
||||
get => RightResizeHandle.Direction;
|
||||
set
|
||||
{
|
||||
LeftResizeHandle.Direction = value;
|
||||
RightResizeHandle.Direction = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal CUISwipeHandle SwipeHandle = new CUISwipeHandle();
|
||||
[CUISerializable]
|
||||
public bool Swipeable
|
||||
{
|
||||
get => SwipeHandle.Swipeable;
|
||||
set => SwipeHandle.Swipeable = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for text, should be in CUITextBlock really
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public Vector2 Padding
|
||||
{
|
||||
get => CUIProps.Padding.Value;
|
||||
set => CUIProps.Padding.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Should be one texture, not sprite sheet
|
||||
/// Or there would be no way to wrap it
|
||||
/// Top side will always point outwards
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUISprite BorderSprite { get; set; } = CUISprite.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Container for Color and Thickness
|
||||
/// Border is drawn inside the component and will eat space from content
|
||||
/// If "by side" border prop != null then it'll take presidence
|
||||
/// </summary>
|
||||
[CUISerializable] public CUIBorder Border { get; set; } = new CUIBorder();
|
||||
[CUISerializable] public CUIBorder TopBorder { get; set; }
|
||||
[CUISerializable] public CUIBorder RigthBorder { get; set; }
|
||||
[CUISerializable] public CUIBorder BottomBorder { get; set; }
|
||||
[CUISerializable] public CUIBorder LeftBorder { get; set; }
|
||||
|
||||
|
||||
[CUISerializable]
|
||||
public float OutlineThickness { get; set; } = 1f;
|
||||
/// <summary>
|
||||
/// Outline is like a border, but on the outside of the component
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public Color OutlineColor
|
||||
{
|
||||
get => CUIProps.OutlineColor.Value;
|
||||
set => CUIProps.OutlineColor.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Will be drawn in background with BackgroundColor
|
||||
/// Default is solid white 1x1 texture
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUISprite BackgroundSprite
|
||||
{
|
||||
get => CUIProps.BackgroundSprite.Value;
|
||||
set => CUIProps.BackgroundSprite.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// If true, mouse events on transparent pixels will be ignored
|
||||
/// Note: this will buffer texture data and potentially consume a lot of memory
|
||||
/// so use wisely
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public bool IgnoreTransparent
|
||||
{
|
||||
get => CUIProps.IgnoreTransparent.Value;
|
||||
set => CUIProps.IgnoreTransparent.SetValue(value);
|
||||
}
|
||||
//TODO i think those colors could be stored inside sprites
|
||||
// But then it'll be much harder to apply side effects, think about it
|
||||
/// <summary>
|
||||
/// Color of BackgroundSprite, default is black
|
||||
/// If you're using custom sprite and don't see it make sure this color is not black
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get => CUIProps.BackgroundColor.Value;
|
||||
set => CUIProps.BackgroundColor.SetValue(value);
|
||||
}
|
||||
|
||||
private float transparency = 1.0f;
|
||||
public float Transparency
|
||||
{
|
||||
get => transparency;
|
||||
set
|
||||
{
|
||||
transparency = value;
|
||||
foreach (CUIComponent child in Children)
|
||||
{
|
||||
if (!child.IgnoreParentTransparency) child.Transparency = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// This palette will be used to resolve palette styles
|
||||
/// Primary, Secondary, Tertiary, Quaternary
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public PaletteOrder Palette
|
||||
{
|
||||
get => CUIProps.Palette.Value;
|
||||
set => CUIProps.Palette.SetValue(value);
|
||||
}
|
||||
public PaletteOrder DeepPalette
|
||||
{
|
||||
set
|
||||
{
|
||||
Palette = value;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.DeepPalette = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Had to expose resize handle props, because it's not a real component
|
||||
/// and can't really use styles
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public Color ResizeHandleColor { get; set; } = Color.White;
|
||||
[CUISerializable]
|
||||
public Color ResizeHandleGrabbedColor { get; set; } = Color.Cyan;
|
||||
|
||||
/// <summary>
|
||||
/// don't
|
||||
/// </summary>
|
||||
public SamplerState SamplerState { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Should children be cut off by scissor rect, this is just visual, it's not the same as culling
|
||||
/// </summary>
|
||||
[CUISerializable] public bool HideChildrenOutsideFrame { get; set; }
|
||||
/// <summary>
|
||||
/// if child rect doesn't intersect with parent it won't be drawn and won't consume fps
|
||||
/// It also sets HideChildrenOutsideFrame
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public bool CullChildren
|
||||
{
|
||||
get => CUIProps.CullChildren.Value;
|
||||
set => CUIProps.CullChildren.SetValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// It shouldn't be culled off even outside of parent bounds and even if parent demands so
|
||||
/// </summary>
|
||||
[CUISerializable] public bool UnCullable { get; set; }
|
||||
/// <summary>
|
||||
/// Will shift all children by this much, e.g. this is how scroll works
|
||||
/// It's also 3D
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUI3DOffset ChildrenOffset
|
||||
{
|
||||
get => CUIProps.ChildrenOffset.Value;
|
||||
set => CUIProps.ChildrenOffset.SetValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limits to children positions
|
||||
/// </summary>
|
||||
public Func<CUIRect, CUIBoundaries> ChildrenBoundaries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should it ignore child offset?
|
||||
/// </summary>
|
||||
[CUISerializable] public bool Fixed { get; set; }
|
||||
/// <summary>
|
||||
/// this point of this component
|
||||
/// </summary>
|
||||
[CUISerializable] public Vector2 Anchor { get; set; }
|
||||
/// <summary>
|
||||
/// will be attached to this point of parent
|
||||
/// </summary>
|
||||
[CUISerializable] public Vector2? ParentAnchor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ghost components don't affect layout
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUIBool2 Ghost
|
||||
{
|
||||
get => CUIProps.Ghost.Value;
|
||||
set => CUIProps.Ghost.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Components are drawn in order of their ZIndex
|
||||
/// Normally it's derived from component position in the tree,
|
||||
/// but this will override it
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public int? ZIndex
|
||||
{
|
||||
get => CUIProps.ZIndex.Value;
|
||||
set => CUIProps.ZIndex.SetValue(value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If true component will set it's Absolute size to sprite texture size
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public bool ResizeToSprite
|
||||
{
|
||||
get => CUIProps.ResizeToSprite.Value;
|
||||
set => CUIProps.ResizeToSprite.SetValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will be resized to fill empty space in list components
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUIBool2 FillEmptySpace
|
||||
{
|
||||
get => CUIProps.FillEmptySpace.Value;
|
||||
set => CUIProps.FillEmptySpace.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Will resize itself to fit components with absolute size, e.g. text
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUIBool2 FitContent
|
||||
{
|
||||
get => CUIProps.FitContent.Value;
|
||||
set => CUIProps.FitContent.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Absolute size and position in pixels
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUINullRect Absolute
|
||||
{
|
||||
get => CUIProps.Absolute.Value;
|
||||
set => CUIProps.Absolute.SetValue(value);
|
||||
}
|
||||
[CUISerializable]
|
||||
public CUINullRect AbsoluteMin
|
||||
{
|
||||
get => CUIProps.AbsoluteMin.Value;
|
||||
set => CUIProps.AbsoluteMin.SetValue(value);
|
||||
}
|
||||
[CUISerializable]
|
||||
public CUINullRect AbsoluteMax
|
||||
{
|
||||
get => CUIProps.AbsoluteMax.Value;
|
||||
set => CUIProps.AbsoluteMax.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Relative to parent size and position, [0..1]
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUINullRect Relative
|
||||
{
|
||||
get => CUIProps.Relative.Value;
|
||||
set => CUIProps.Relative.SetValue(value);
|
||||
}
|
||||
[CUISerializable]
|
||||
public CUINullRect RelativeMin
|
||||
{
|
||||
get => CUIProps.RelativeMin.Value;
|
||||
set => CUIProps.RelativeMin.SetValue(value);
|
||||
}
|
||||
[CUISerializable]
|
||||
public CUINullRect RelativeMax
|
||||
{
|
||||
get => CUIProps.RelativeMax.Value;
|
||||
set => CUIProps.RelativeMax.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// It's like Relative, but to the opposite dimension
|
||||
/// E.g. Real.Width = CrossRelative.Width * Parent.Real.Height
|
||||
/// Handy for creating square things
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUINullRect CrossRelative
|
||||
{
|
||||
get => CUIProps.CrossRelative.Value;
|
||||
set => CUIProps.CrossRelative.SetValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used in Grid, space separated Row sizes, either in pixels (123) or in % (123%)
|
||||
/// </summary>
|
||||
[CUISerializable] public string GridTemplateRows { get; set; }
|
||||
/// <summary>
|
||||
/// Used in Grid, space separated Columns sizes, either in pixels (123) or in % (123%)
|
||||
/// </summary>
|
||||
[CUISerializable] public string GridTemplateColumns { get; set; }
|
||||
/// <summary>
|
||||
/// Component will be placed in this cell in the grid component
|
||||
/// </summary>
|
||||
[CUISerializable] public Point? GridStartCell { get; set; }
|
||||
/// <summary>
|
||||
/// And resized to fit cells from GridStartCell to GridEndCell
|
||||
/// </summary>
|
||||
[CUISerializable] public Point? GridEndCell { get; set; }
|
||||
/// <summary>
|
||||
/// Sets both GridStartCell and GridEndCell at once
|
||||
/// </summary>
|
||||
public Point? GridCell
|
||||
{
|
||||
get => GridStartCell;
|
||||
set
|
||||
{
|
||||
GridStartCell = value;
|
||||
GridEndCell = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
#region Layout --------------------------------------------------------
|
||||
|
||||
protected CUILayout layout;
|
||||
//[CUISerializable]
|
||||
public virtual CUILayout Layout
|
||||
{
|
||||
get => layout;
|
||||
set { layout = value; layout.Host = this; }
|
||||
}
|
||||
|
||||
public event Action OnLayoutUpdated;
|
||||
public void InvokeOnLayoutUpdated() => OnLayoutUpdated?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// Triggers recalculation of layouts from parent and below
|
||||
/// </summary>
|
||||
internal void OnPropChanged([CallerMemberName] string memberName = "")
|
||||
{
|
||||
Layout.Changed = true;
|
||||
CUIDebug.Capture(null, this, "OnPropChanged", memberName, "Layout.Changed", "true");
|
||||
MainComponent?.LayoutChanged();
|
||||
}
|
||||
internal void OnSelfAndParentChanged([CallerMemberName] string memberName = "")
|
||||
{
|
||||
Layout.SelfAndParentChanged = true;
|
||||
CUIDebug.Capture(null, this, "OnSelfAndParentChanged", memberName, "Layout.SelfAndParentChanged", "true");
|
||||
MainComponent?.LayoutChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers recalc of own pseudo components and nothing else
|
||||
/// </summary>
|
||||
internal void OnDecorPropChanged([CallerMemberName] string memberName = "")
|
||||
{
|
||||
Layout.DecorChanged = true;
|
||||
CUIDebug.Capture(null, this, "OnDecorPropChanged", memberName, "Layout.DecorChanged", "true");
|
||||
MainComponent?.LayoutChanged();
|
||||
}
|
||||
/// <summary>
|
||||
/// Notifies parent (only) than it may need to ResizeToContent
|
||||
/// </summary>
|
||||
internal void OnAbsolutePropChanged([CallerMemberName] string memberName = "")
|
||||
{
|
||||
Layout.AbsoluteChanged = true;
|
||||
CUIDebug.Capture(null, this, "OnAbsolutePropChanged", memberName, "Layout.AbsoluteChanged", "true");
|
||||
MainComponent?.LayoutChanged();
|
||||
}
|
||||
/// <summary>
|
||||
/// Triggers recalculation of layouts from this and below
|
||||
/// </summary>
|
||||
internal void OnChildrenPropChanged([CallerMemberName] string memberName = "")
|
||||
{
|
||||
Layout.ChildChanged = true;
|
||||
MainComponent?.LayoutChanged();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
//HACK This is potentially cursed
|
||||
/// <summary>
|
||||
/// Arbitrary data
|
||||
/// </summary>
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Will prevent serialization to xml if true
|
||||
/// </summary>
|
||||
public bool Unserializable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this a serialization cutoff point
|
||||
/// Parent will serialize children down to this component
|
||||
/// Further serialization should be hadled by this component
|
||||
/// </summary>
|
||||
[CUISerializable] public bool BreakSerialization { get; set; }
|
||||
/// <summary>
|
||||
/// Some props (like visible) are autopassed to all new childs
|
||||
/// see PassPropsToChild
|
||||
/// </summary>
|
||||
[CUISerializable] public bool ShouldPassPropsToChildren { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Don't inherit parent Visibility
|
||||
/// </summary>
|
||||
[CUISerializable] public bool IgnoreParentVisibility { get; set; }
|
||||
/// <summary>
|
||||
/// Don't inherit parent IgnoreEvents
|
||||
/// </summary>
|
||||
[CUISerializable] public bool IgnoreParentEventIgnorance { get; set; }
|
||||
/// <summary>
|
||||
/// Don't inherit parent ZIndex
|
||||
/// </summary>
|
||||
[CUISerializable] public bool IgnoreParentZIndex { get; set; }
|
||||
[CUISerializable] public bool IgnoreParentTransparency { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invisible components are not drawn, but still can be interacted with
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public bool Visible
|
||||
{
|
||||
get => CUIProps.Visible.Value;
|
||||
set => CUIProps.Visible.SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Won't react to mouse events
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public bool IgnoreEvents
|
||||
{
|
||||
get => CUIProps.IgnoreEvents.Value;
|
||||
set => CUIProps.IgnoreEvents.SetValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visible + !IgnoreEvents
|
||||
/// </summary>
|
||||
public bool Revealed
|
||||
{
|
||||
get => CUIProps.Revealed.Value;
|
||||
set => CUIProps.Revealed.SetValue(value);
|
||||
}
|
||||
|
||||
|
||||
//HACK this is meant for buttons, but i want to access it on generic components in CUIMap
|
||||
protected bool disabled;
|
||||
/// <summary>
|
||||
/// Usually means - non interactable, e.g. unclickable gray button
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public virtual bool Disabled
|
||||
{
|
||||
get => disabled;
|
||||
set => disabled = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
public record CompareResult(bool equal, string firstMismatch = "")
|
||||
{
|
||||
public static implicit operator bool(CompareResult r) => r.equal;
|
||||
}
|
||||
|
||||
public static bool DeepCompareVerbose(CUIComponent a, CUIComponent b)
|
||||
{
|
||||
CompareResult result = DeepCompare(a, b);
|
||||
if (result.equal) CUI.Log($"{a} == {b}");
|
||||
else CUI.Log($"{result.firstMismatch}");
|
||||
return result.equal;
|
||||
}
|
||||
public static CompareResult DeepCompare(CUIComponent a, CUIComponent b)
|
||||
{
|
||||
if (a.GetType() != b.GetType()) return new CompareResult(false, $"type mismatch: {a} | {b}");
|
||||
|
||||
Type T = a.GetType();
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(T);
|
||||
|
||||
foreach (var (key, pi) in meta.Serializable)
|
||||
{
|
||||
if (!object.Equals(pi.GetValue(a), pi.GetValue(b)))
|
||||
{
|
||||
return new CompareResult(false, $"{pi}: {a}{pi.GetValue(a)} | {b}{pi.GetValue(b)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (a.Children.Count != b.Children.Count)
|
||||
{
|
||||
return new CompareResult(false, $"child count mismatch: {a}{CUI.ArrayToString(a.Children)} | {b}{CUI.ArrayToString(b.Children)}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < a.Children.Count; i++)
|
||||
{
|
||||
CompareResult sub = DeepCompare(a.Children[i], b.Children[i]);
|
||||
if (!sub.equal) return sub;
|
||||
}
|
||||
|
||||
return new CompareResult(true);
|
||||
}
|
||||
|
||||
#region State --------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// State is just a clone component with copies of all props
|
||||
/// </summary>
|
||||
public Dictionary<string, CUIComponent> States { get; set; } = new();
|
||||
// TODO why all clones are unreal? this is sneaky, and i don't remember what's it for
|
||||
public CUIComponent Clone()
|
||||
{
|
||||
CUIComponent clone = new CUIComponent()
|
||||
{
|
||||
Unreal = true,
|
||||
};
|
||||
clone.ApplyState(this);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public void SaveStateAs(string name) => States[name] = this.Clone();
|
||||
public void LoadState(string name) => ApplyState(States.GetValueOrDefault(name));
|
||||
public void ForgetState(string name) => States.Remove(name);
|
||||
|
||||
//TODO think about edge cases (PassPropsToChild)
|
||||
public void ApplyState(CUIComponent state)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
if (state == null) return;
|
||||
|
||||
//TODO why not closest relative?
|
||||
Type targetType = state.GetType() == GetType() ? GetType() : typeof(CUIComponent);
|
||||
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(targetType);
|
||||
|
||||
//TODO Megacringe, fix it
|
||||
foreach (PropertyInfo pi in meta.Serializable.Values)
|
||||
{
|
||||
if (pi.PropertyType.IsValueType || pi.PropertyType == typeof(string))
|
||||
{
|
||||
pi.SetValue(this, pi.GetValue(state));
|
||||
}
|
||||
else
|
||||
{
|
||||
object value = pi.GetValue(state);
|
||||
if (value == null)
|
||||
{
|
||||
pi.SetValue(this, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pi.PropertyType.IsAssignableTo(typeof(ICloneable)))
|
||||
{
|
||||
ICloneable cloneable = (ICloneable)pi.GetValue(state);
|
||||
object clone = cloneable.Clone();
|
||||
pi.SetValue(this, clone);
|
||||
}
|
||||
else
|
||||
{
|
||||
CUI.Info($"Ekhem, can't copy {pi} prop from {state} to {this} because it's not cloneable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO Megacringe, fix it
|
||||
foreach (PropertyInfo pi in meta.Serializable.Values)
|
||||
{
|
||||
if (pi.PropertyType.IsValueType && !object.Equals(pi.GetValue(state), pi.GetValue(this)))
|
||||
{
|
||||
pi.SetValue(this, pi.GetValue(state));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region XML --------------------------------------------------------
|
||||
|
||||
public static bool ForceSaveAllProps { get; set; } = false;
|
||||
public static bool SaveAfterLoad { get; set; } = true;
|
||||
|
||||
public string SavePath { get; set; }
|
||||
|
||||
public virtual XElement ToXML(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Unserializable) return null;
|
||||
|
||||
Type type = GetType();
|
||||
|
||||
XElement e = new XElement(type.Name);
|
||||
|
||||
PackProps(e, propAttribute);
|
||||
|
||||
foreach (CUIComponent child in Children)
|
||||
{
|
||||
if (!this.BreakSerialization)
|
||||
{
|
||||
e.Add(child.ToXML(propAttribute));
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
return new XElement("Error", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void FromXML(XElement element, string baseFolder = null)
|
||||
{
|
||||
foreach (XElement childElement in element.Elements())
|
||||
{
|
||||
Type childType = CUIReflection.GetComponentTypeByName(childElement.Name.ToString());
|
||||
if (childType == null) continue;
|
||||
|
||||
CUIComponent child = (CUIComponent)Activator.CreateInstance(childType);
|
||||
child.FromXML(childElement, baseFolder);
|
||||
|
||||
//CUI.Log($"{this}[{child.AKA}] = {child} ");
|
||||
this.Append(child, child.AKA);
|
||||
}
|
||||
|
||||
ExtractProps(element, baseFolder);
|
||||
}
|
||||
|
||||
protected void ExtractProps(XElement element, string baseFolder = null)
|
||||
{
|
||||
Type type = GetType();
|
||||
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(type);
|
||||
|
||||
foreach (XAttribute attribute in element.Attributes())
|
||||
{
|
||||
if (!meta.Serializable.ContainsKey(attribute.Name.ToString()))
|
||||
{
|
||||
CUIDebug.Error($"Can't parse prop {attribute.Name} in {type.Name} because type metadata doesn't contain that prop (is it a property? fields aren't supported yet)");
|
||||
continue;
|
||||
}
|
||||
|
||||
PropertyInfo prop = meta.Serializable[attribute.Name.ToString()];
|
||||
|
||||
MethodInfo parse = null;
|
||||
if (CUIExtensions.Parse.ContainsKey(prop.PropertyType))
|
||||
{
|
||||
parse = CUIExtensions.Parse[prop.PropertyType];
|
||||
}
|
||||
|
||||
parse ??= prop.PropertyType.GetMethod(
|
||||
"Parse",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
new Type[] { typeof(string) }
|
||||
);
|
||||
|
||||
|
||||
Func<string, object> ParseWithContext = null;
|
||||
//HACK
|
||||
if (prop.PropertyType == typeof(CUISprite) && baseFolder != null)
|
||||
{
|
||||
ParseWithContext = (raw) => CUISprite.ParseWithContext(raw, baseFolder);
|
||||
}
|
||||
|
||||
|
||||
if (parse == null)
|
||||
{
|
||||
if (prop.PropertyType.IsEnum)
|
||||
{
|
||||
try
|
||||
{
|
||||
prop.SetValue(this, Enum.Parse(prop.PropertyType, attribute.Value));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUIDebug.Error($"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CUIDebug.Error($"Can't parse prop {prop.Name} in {type.Name} because it's type {prop.PropertyType.Name} is missing Parse method");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
object result = null;
|
||||
if (ParseWithContext != null)
|
||||
{
|
||||
result = ParseWithContext(attribute.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = parse.Invoke(null, new object[] { attribute.Value });
|
||||
}
|
||||
prop.SetValue(this, result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUIDebug.Error($"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void PackProps(XElement element, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
Type type = GetType();
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(type);
|
||||
|
||||
SortedDictionary<string, PropertyInfo> props = propAttribute switch
|
||||
{
|
||||
CUIAttribute.CUISerializable => meta.Serializable,
|
||||
CUIAttribute.Calculated => meta.Calculated,
|
||||
_ => meta.Serializable,
|
||||
};
|
||||
|
||||
foreach (string key in props.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
object value = props[key].GetValue(this);
|
||||
// it's default value for this prop
|
||||
if (!ForceSaveAllProps && meta.Default != null && Object.Equals(value, CUIReflection.GetNestedValue(meta.Default, key)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MethodInfo customToString = CUIExtensions.CustomToString.GetValueOrDefault(props[key].PropertyType);
|
||||
|
||||
if (customToString != null)
|
||||
{
|
||||
element?.SetAttributeValue(key, customToString.Invoke(null, new object[] { value }));
|
||||
}
|
||||
else
|
||||
{
|
||||
element?.SetAttributeValue(key, value);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Failed to serialize prop: {e.Message}");
|
||||
CUI.Warning($"{key} in {this}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string Serialize(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
try
|
||||
{
|
||||
XElement e = this.ToXML(propAttribute);
|
||||
return e.ToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Error(e);
|
||||
return e.Message;
|
||||
}
|
||||
}
|
||||
public static CUIComponent Deserialize(string raw, string baseFolder = null)
|
||||
{
|
||||
return Deserialize(XElement.Parse(raw));
|
||||
}
|
||||
|
||||
public static CUIComponent Deserialize(XElement e, string baseFolder = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Type type = CUIReflection.GetComponentTypeByName(e.Name.ToString());
|
||||
if (type == null) return null;
|
||||
|
||||
CUIComponent c = (CUIComponent)Activator.CreateInstance(type);
|
||||
// c.RemoveAllChildren();
|
||||
c.FromXML(e, baseFolder);
|
||||
CUIComponent.RunRecursiveOn(c, (component) => component.Hydrate());
|
||||
|
||||
return c;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUIDebug.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadSelfFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
|
||||
RemoveAllChildren();
|
||||
if (searchForSpritesInTheSameFolder) FromXML(xdoc.Root, Path.GetDirectoryName(path));
|
||||
else FromXML(xdoc.Root);
|
||||
|
||||
CUIComponent.RunRecursiveOn(this, (component) => component.Hydrate());
|
||||
SavePath = path;
|
||||
|
||||
if (SaveAfterLoad && saveAfterLoad) SaveToTheSamePath();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUI.Warning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static CUIComponent LoadFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
CUIComponent result;
|
||||
if (searchForSpritesInTheSameFolder)
|
||||
{
|
||||
result = Deserialize(xdoc.Root, Path.GetDirectoryName(path));
|
||||
}
|
||||
else result = Deserialize(xdoc.Root);
|
||||
|
||||
result.SavePath = path;
|
||||
|
||||
if (SaveAfterLoad && saveAfterLoad) result.SaveToTheSamePath();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUIDebug.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static T LoadFromFile<T>(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false) where T : CUIComponent
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
T result;
|
||||
if (searchForSpritesInTheSameFolder)
|
||||
{
|
||||
result = (T)Deserialize(xdoc.Root, Path.GetDirectoryName(path));
|
||||
}
|
||||
else result = (T)Deserialize(xdoc.Root);
|
||||
|
||||
result.SavePath = path;
|
||||
|
||||
if (SaveAfterLoad && saveAfterLoad) result.SaveToTheSamePath();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUIDebug.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromTheSameFile()
|
||||
{
|
||||
if (SavePath == null)
|
||||
{
|
||||
CUI.Warning($"Can't load {this} from The Same Path, SavePath is null");
|
||||
return;
|
||||
}
|
||||
LoadSelfFromFile(SavePath);
|
||||
}
|
||||
|
||||
public void SaveToTheSamePath()
|
||||
{
|
||||
if (SavePath == null)
|
||||
{
|
||||
CUI.Warning($"Can't save {this} To The Same Path, SavePath is null");
|
||||
return;
|
||||
}
|
||||
SaveToFile(SavePath);
|
||||
}
|
||||
|
||||
public void SaveToFile(string path, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = new XDocument();
|
||||
xdoc.Add(this.ToXML(propAttribute));
|
||||
xdoc.Save(path);
|
||||
SavePath = path;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Experimental method
|
||||
/// Here you can add data/ callbacks/ save stuff to variables
|
||||
/// after loading a xml skeletom
|
||||
/// </summary>
|
||||
public virtual void Hydrate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent : IDisposable
|
||||
{
|
||||
private void SetupStyles()
|
||||
{
|
||||
Style = new CUIStyle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use it to e.g. update component color
|
||||
/// </summary>
|
||||
public event Action OnStyleApplied;
|
||||
internal void InvokeOnStyleApplied() => OnStyleApplied?.Invoke();
|
||||
|
||||
private void HandleStylePropChange(string key, string value)
|
||||
{
|
||||
CUIGlobalStyleResolver.OnComponentStylePropChanged(this, key);
|
||||
}
|
||||
private void HandleStyleChange(CUIStyle s)
|
||||
{
|
||||
CUIGlobalStyleResolver.OnComponentStyleChanged(this);
|
||||
}
|
||||
|
||||
private CUIStyle style;
|
||||
/// <summary>
|
||||
/// Allows you to assing parsable string or link to CUIPalette to any prop
|
||||
/// It's indexable, so you can access it like this: component.Style["BackgroundColor"] = "cyan"
|
||||
/// if value starts with "CUIPalette." it will extract the value from palette
|
||||
/// e.g. component.Style["BackgroundColor"] = "CUIPalette.DarkBlue.Secondary.On"
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public CUIStyle Style
|
||||
{
|
||||
get => style;
|
||||
set
|
||||
{
|
||||
if (style == value) return;
|
||||
|
||||
if (style != null)
|
||||
{
|
||||
style.OnUse -= HandleStyleChange;
|
||||
style.OnPropChanged -= HandleStylePropChange;
|
||||
}
|
||||
|
||||
style = value;
|
||||
|
||||
if (style != null)
|
||||
{
|
||||
style.OnUse += HandleStyleChange;
|
||||
style.OnPropChanged += HandleStylePropChange;
|
||||
}
|
||||
|
||||
HandleStyleChange(style);
|
||||
}
|
||||
}
|
||||
|
||||
public CUIStyle ResolvedStyle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
#region Tree --------------------------------------------------------
|
||||
|
||||
public List<CUIComponent> Children { get; set; } = new();
|
||||
|
||||
private CUIComponent? parent; public CUIComponent? Parent
|
||||
{
|
||||
get => parent;
|
||||
set => SetParent(value);
|
||||
}
|
||||
|
||||
internal void SetParent(CUIComponent? value, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
if (parent != null)
|
||||
{
|
||||
TreeChanged = true;
|
||||
OnPropChanged();
|
||||
parent.Forget(this);
|
||||
parent.Children.Remove(this);
|
||||
parent.OnChildRemoved?.Invoke(this);
|
||||
}
|
||||
|
||||
parent = value;
|
||||
|
||||
CUIDebug.Capture(null, this, "SetParent", memberName, "parent", $"{parent}");
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
if (parent is CUIMainComponent main) MainComponent = main;
|
||||
if (parent?.MainComponent != null) MainComponent = parent.MainComponent;
|
||||
|
||||
//parent.Children.Add(this);
|
||||
TreeChanged = true;
|
||||
if (AKA != null) parent.Remember(this, AKA);
|
||||
parent.PassPropsToChild(this);
|
||||
OnPropChanged();
|
||||
parent.OnChildAdded?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool treeChanged = true; internal bool TreeChanged
|
||||
{
|
||||
get => treeChanged;
|
||||
set
|
||||
{
|
||||
treeChanged = value;
|
||||
if (value)
|
||||
{
|
||||
OnTreeChanged?.Invoke();
|
||||
if (Parent != null) Parent.TreeChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to add array of children
|
||||
/// </summary>
|
||||
public IEnumerable<CUIComponent> AddChildren
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (CUIComponent c in value) { Append(c); }
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<CUIComponent> OnChildAdded;
|
||||
public event Action<CUIComponent> OnChildRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Adds children to the end of the list
|
||||
/// </summary>
|
||||
/// <param name="child"></param>
|
||||
/// <param name="name"> AKA </param>
|
||||
/// <returns> child </returns>
|
||||
public virtual CUIComponent Append(CUIComponent child, string name = null, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
if (child == null) return child;
|
||||
|
||||
child.Parent = this;
|
||||
Children.Add(child);
|
||||
if (name != null) Remember(child, name);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds children to the begining of the list
|
||||
/// </summary>
|
||||
/// <param name="child"></param>
|
||||
/// <param name="name"> AKA </param>
|
||||
/// <returns> child </returns>
|
||||
public virtual CUIComponent Prepend(CUIComponent child, string name = null, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
if (child == null) return child;
|
||||
|
||||
child.Parent = this;
|
||||
Children.Insert(0, child);
|
||||
if (name != null) Remember(child, name);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
public virtual CUIComponent Insert(CUIComponent child, int index, string name = null, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
if (child == null) return child;
|
||||
|
||||
child.Parent = this;
|
||||
index = Math.Clamp(index, 0, Children.Count);
|
||||
Children.Insert(index, child);
|
||||
if (name != null) Remember(child, name);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
//TODO DRY
|
||||
public void RemoveSelf() => Parent?.RemoveChild(this);
|
||||
public CUIComponent RemoveChild(CUIComponent child, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
if (child == null || !Children.Contains(child)) return child;
|
||||
|
||||
if (this != null) // kek
|
||||
{
|
||||
child.TreeChanged = true;
|
||||
child.OnPropChanged();
|
||||
//HACK i'm sure it doesn't belong here, find a better place
|
||||
forsedSize = new CUINullVector2();
|
||||
OnAbsolutePropChanged();
|
||||
// Forget(child);
|
||||
Children.Remove(child);
|
||||
OnChildRemoved?.Invoke(child);
|
||||
}
|
||||
|
||||
child.parent = null;
|
||||
|
||||
CUIDebug.Capture(null, this, "RemoveChild", memberName, "child", $"{child}");
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
//TODO DRY
|
||||
public void RemoveAllChildren([CallerMemberName] string memberName = "")
|
||||
{
|
||||
foreach (CUIComponent c in Children)
|
||||
{
|
||||
if (this != null) // kek
|
||||
{
|
||||
c.TreeChanged = true;
|
||||
c.OnPropChanged();
|
||||
//Forget(c);
|
||||
//Children.Remove(c);
|
||||
OnChildRemoved?.Invoke(c);
|
||||
}
|
||||
|
||||
c.parent = null;
|
||||
|
||||
CUIDebug.Capture(null, this, "RemoveAllChildren", memberName, "child", $"{c}");
|
||||
}
|
||||
|
||||
NamedComponents.Clear();
|
||||
Children.Clear();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Pass props like ZIndex, Visible to a new child
|
||||
/// </summary>
|
||||
/// <param name="child"></param>
|
||||
protected virtual void PassPropsToChild(CUIComponent child)
|
||||
{
|
||||
if (!ShouldPassPropsToChildren) return;
|
||||
|
||||
//child.Palette = Palette;
|
||||
if (ZIndex.HasValue && !child.IgnoreParentZIndex) child.ZIndex = ZIndex.Value + 1;
|
||||
if (IgnoreEvents && !child.IgnoreParentEventIgnorance) child.IgnoreEvents = true;
|
||||
if (!Visible && !child.IgnoreParentVisibility) child.Visible = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using HarmonyLib;
|
||||
using System.Threading;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all components
|
||||
/// </summary>
|
||||
public partial class CUIComponent : IDisposable
|
||||
{
|
||||
#region Static --------------------------------------------------------
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () =>
|
||||
{
|
||||
MaxID = 0;
|
||||
};
|
||||
|
||||
CUI.OnDispose += () =>
|
||||
{
|
||||
foreach (int id in ComponentsById.Keys)
|
||||
{
|
||||
CUIComponent component = null;
|
||||
ComponentsById[id].TryGetTarget(out component);
|
||||
component?.Dispose();
|
||||
}
|
||||
|
||||
ComponentsById.Clear();
|
||||
ComponentsByType.Clear();
|
||||
|
||||
|
||||
dummyComponent = null;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal static int MaxID;
|
||||
public static Dictionary<int, WeakReference<CUIComponent>> ComponentsById = new();
|
||||
public static WeakCatalog<Type, CUIComponent> ComponentsByType = new();
|
||||
|
||||
/// <summary>
|
||||
/// This is used to trick vanilla GUI into believing that
|
||||
/// mouse is hovering some component and block clicks
|
||||
/// </summary>
|
||||
public static GUIButton dummyComponent = new GUIButton(new RectTransform(new Point(0, 0)))
|
||||
{
|
||||
Text = "DUMMY",
|
||||
};
|
||||
/// <summary>
|
||||
/// designed to be versatile, in fact never used
|
||||
/// </summary>
|
||||
public static void RunRecursiveOn(CUIComponent component, Action<CUIComponent> action)
|
||||
{
|
||||
action(component);
|
||||
foreach (CUIComponent child in component.Children)
|
||||
{
|
||||
RunRecursiveOn(child, action);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ForEach(Action<CUIComponent> action)
|
||||
{
|
||||
foreach (int id in ComponentsById.Keys)
|
||||
{
|
||||
CUIComponent component = null;
|
||||
ComponentsById[id].TryGetTarget(out component);
|
||||
if (component is not null) action(component);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetClassHierarchy(Type type)
|
||||
{
|
||||
while (type != typeof(Object) && type != null)
|
||||
{
|
||||
yield return type;
|
||||
type = type.BaseType;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetReverseClassHierarchy(Type type)
|
||||
=> CUIComponent.GetClassHierarchy(type).Reverse<Type>();
|
||||
|
||||
#endregion
|
||||
#region Virtual --------------------------------------------------------
|
||||
|
||||
|
||||
//TODO move to cui props, it's a bit more clampicated than ChildrenBoundaries
|
||||
/// <summary>
|
||||
/// Bounds for offset, e.g. scroll, zoom
|
||||
/// </summary>
|
||||
internal virtual CUIBoundaries ChildOffsetBounds => new CUIBoundaries();
|
||||
/// <summary>
|
||||
/// "Component like" ghost stuff that can't have children and
|
||||
/// doesn't impact layout. Drag handles, text etc
|
||||
/// </summary>
|
||||
internal virtual void UpdatePseudoChildren()
|
||||
{
|
||||
LeftResizeHandle.Update();
|
||||
RightResizeHandle.Update();
|
||||
}
|
||||
/// <summary>
|
||||
/// Last chance to disagree with proposed size
|
||||
/// For stuff that should resize to content
|
||||
/// </summary>
|
||||
/// <param name="size"> proposed size </param>
|
||||
/// <returns> size you're ok with </returns>
|
||||
internal virtual Vector2 AmIOkWithThisSize(Vector2 size) => size;
|
||||
/// <summary>
|
||||
/// Here component should be drawn
|
||||
/// </summary>
|
||||
/// <param name="spriteBatch"></param>
|
||||
public virtual partial void Draw(SpriteBatch spriteBatch);
|
||||
/// <summary>
|
||||
/// Method for drawing something that should always be on top, e.g. resize handles
|
||||
/// </summary>
|
||||
/// <param name="spriteBatch"></param>
|
||||
public virtual partial void DrawFront(SpriteBatch spriteBatch);
|
||||
|
||||
#endregion
|
||||
#region Draw --------------------------------------------------------
|
||||
|
||||
public virtual partial void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (BackgroundVisible) CUI.DrawRectangle(spriteBatch, Real, BackgroundColor * Transparency, BackgroundSprite);
|
||||
|
||||
CUI.DrawBorders(spriteBatch, this);
|
||||
// if (Border.Visible) GUI.DrawRectangle(spriteBatch, BorderBox.Position, BorderBox.Size, Border.Color, thickness: Border.Thickness);
|
||||
|
||||
if (OutlineVisible) GUI.DrawRectangle(spriteBatch, OutlineBox.Position, OutlineBox.Size, OutlineColor, thickness: OutlineThickness);
|
||||
|
||||
LeftResizeHandle.Draw(spriteBatch);
|
||||
RightResizeHandle.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
public virtual partial void DrawFront(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (DebugHighlight)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, Real.Position, Real.Size, Color.Cyan * 0.5f, isFilled: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#region Constructors --------------------------------------------------------
|
||||
|
||||
|
||||
internal void Vitalize()
|
||||
{
|
||||
foreach (FieldInfo fi in this.GetType().GetFields(AccessTools.all))
|
||||
{
|
||||
if (fi.FieldType.IsAssignableTo(typeof(ICUIVitalizable)))
|
||||
{
|
||||
ICUIVitalizable prop = (ICUIVitalizable)fi.GetValue(this);
|
||||
if (prop == null) continue;
|
||||
prop.SetHost(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void VitalizeProps()
|
||||
{
|
||||
foreach (FieldInfo fi in this.GetType().GetFields(AccessTools.all))
|
||||
{
|
||||
if (fi.FieldType.IsAssignableTo(typeof(ICUIProp)))
|
||||
{
|
||||
ICUIProp prop = (ICUIProp)fi.GetValue(this);
|
||||
if (prop == null) continue; // this is for Main.GrabbedDragHandle
|
||||
prop.SetHost(this);
|
||||
prop.SetName(fi.Name);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FieldInfo fi in typeof(CUIComponentProps).GetFields(AccessTools.all))
|
||||
{
|
||||
if (fi.FieldType.IsAssignableTo(typeof(ICUIProp)))
|
||||
{
|
||||
ICUIProp prop = (ICUIProp)fi.GetValue(CUIProps);
|
||||
if (prop == null) continue;
|
||||
prop.SetHost(this);
|
||||
prop.SetName(fi.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CUIComponent()
|
||||
{
|
||||
if (CUI.Disposed)
|
||||
{
|
||||
Disposed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ID = MaxID++;
|
||||
|
||||
ComponentsById[ID] = new WeakReference<CUIComponent>(this);
|
||||
ComponentsByType.Add(this.GetType(), this);
|
||||
|
||||
Vitalize();
|
||||
VitalizeProps();
|
||||
|
||||
SetupCommands();
|
||||
|
||||
Layout = new CUILayoutSimple();
|
||||
|
||||
SetupStyles();
|
||||
SetupAnimations();
|
||||
}
|
||||
|
||||
public CUIComponent(float? x = null, float? y = null, float? w = null, float? h = null) : this()
|
||||
{
|
||||
Relative = new CUINullRect(x, y, w, h);
|
||||
}
|
||||
|
||||
public bool Disposed;
|
||||
public void Dispose()
|
||||
{
|
||||
if (Disposed) return;
|
||||
CleanUp();
|
||||
Disposed = true;
|
||||
}
|
||||
public virtual void CleanUp() { }
|
||||
|
||||
~CUIComponent() => Dispose();
|
||||
|
||||
public override string ToString() => $"{this.GetType().Name}:{ID}:{AKA}";
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Drop down list, aka Select
|
||||
/// </summary>
|
||||
public class CUIDropDown : CUIComponent
|
||||
{
|
||||
internal class DDOption : CUIButton
|
||||
{
|
||||
public DDOption() : this("") { }
|
||||
public DDOption(string text) : base(text) { }
|
||||
}
|
||||
private CUIButton MainButton;
|
||||
private CUIVerticalList OptionBox;
|
||||
|
||||
/// <summary>
|
||||
/// List of options
|
||||
/// Options are just strings
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public IEnumerable<string> Options
|
||||
{
|
||||
get => OptionBox.Children.Cast<DDOption>().Select(o => o.Text);
|
||||
set
|
||||
{
|
||||
Clear();
|
||||
foreach (string option in value) { Add(option); }
|
||||
}
|
||||
}
|
||||
[CUISerializable]
|
||||
public string Selected
|
||||
{
|
||||
get => MainButton.Text;
|
||||
set => Select(value);
|
||||
}
|
||||
|
||||
public event Action<string> OnSelect;
|
||||
public Action<string> AddOnSelect { set { OnSelect += value; } }
|
||||
|
||||
|
||||
public void Open() => OptionBox.Revealed = true;
|
||||
public void Close() => OptionBox.Revealed = false;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
OptionBox.RemoveAllChildren();
|
||||
Select("");
|
||||
}
|
||||
|
||||
public void Add(string option)
|
||||
{
|
||||
OptionBox.Append(new DDOption(option)
|
||||
{
|
||||
AddOnMouseDown = (e) => Select(option),
|
||||
});
|
||||
}
|
||||
|
||||
public void Select(int i) => Select(Options.ElementAtOrDefault(i));
|
||||
public void Select(string option)
|
||||
{
|
||||
MainButton.Text = option ?? "";
|
||||
OptionBox.Revealed = false;
|
||||
OnSelect?.Invoke(MainButton.Text);
|
||||
}
|
||||
|
||||
public void Remove(int i) => Remove(Options.ElementAtOrDefault(i));
|
||||
public void Remove(string option)
|
||||
{
|
||||
if (option == null) return;
|
||||
if (!Options.Contains(option)) return;
|
||||
|
||||
DDOption ddoption = OptionBox.Children.Cast<DDOption>().FirstOrDefault(o => o.Text == option);
|
||||
bool wasSelected = MainButton.Text == ddoption.Text;
|
||||
OptionBox.RemoveChild(ddoption);
|
||||
if (wasSelected) Select(0);
|
||||
}
|
||||
|
||||
public CUIDropDown() : base()
|
||||
{
|
||||
BreakSerialization = true;
|
||||
OptionBox = new CUIVerticalList()
|
||||
{
|
||||
Relative = new CUINullRect(w: 1),
|
||||
FitContent = new CUIBool2(true, true),
|
||||
Ghost = new CUIBool2(false, true),
|
||||
Anchor = CUIAnchor.TopLeft,
|
||||
ParentAnchor = CUIAnchor.BottomLeft,
|
||||
ZIndex = 500,
|
||||
Style = new CUIStyle(){
|
||||
{"BackgroundColor", "CUIPalette.DDOption.Background"},
|
||||
{"Border", "CUIPalette.DDOption.Border"},
|
||||
},
|
||||
};
|
||||
|
||||
MainButton = new CUIButton()
|
||||
{
|
||||
Text = "CUIDropDown",
|
||||
Relative = new CUINullRect(w: 1, h: 1),
|
||||
AddOnMouseDown = (e) => OptionBox.Revealed = !OptionBox.Revealed,
|
||||
};
|
||||
|
||||
Append(MainButton);
|
||||
Append(OptionBox);
|
||||
|
||||
FitContent = new CUIBool2(true, true);
|
||||
|
||||
//HACK Why this main is hardcoded?
|
||||
//in static constructor CUI.Main is null and this won't work
|
||||
if (CUI.Main is not null) CUI.Main.OnMouseDown += (e) => Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Draggable and resizable container for other components
|
||||
/// </summary>
|
||||
public class CUIFrame : CUIComponent
|
||||
{
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (BackgroundVisible) CUI.DrawRectangle(spriteBatch, Real, BackgroundColor, BackgroundSprite);
|
||||
}
|
||||
|
||||
public override void DrawFront(SpriteBatch spriteBatch)
|
||||
{
|
||||
//if (BorderVisible) CUI.DrawBorders(spriteBatch, Real, BorderColor, BorderSprite, BorderThickness);
|
||||
// GUI.DrawRectangle(spriteBatch, BorderBox.Position, BorderBox.Size, BorderColor, thickness: BorderThickness);
|
||||
CUI.DrawBorders(spriteBatch, this);
|
||||
|
||||
if (OutlineVisible) GUI.DrawRectangle(spriteBatch, OutlineBox.Position, OutlineBox.Size, OutlineColor, thickness: OutlineThickness);
|
||||
|
||||
LeftResizeHandle.Draw(spriteBatch);
|
||||
RightResizeHandle.Draw(spriteBatch);
|
||||
|
||||
//base.DrawFront(spriteBatch);
|
||||
}
|
||||
|
||||
public event Action OnOpen;
|
||||
public event Action OnClose;
|
||||
|
||||
/// <summary>
|
||||
/// This will reveal the frame and append it to CUI.Main
|
||||
/// </summary>
|
||||
public void Open()
|
||||
{
|
||||
if (CUI.Main == null && Parent != CUI.Main) return;
|
||||
CUI.Main.Append(this);
|
||||
Revealed = true;
|
||||
OnOpen?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will hide the frame and remove it from children of CUI.Main
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
RemoveSelf();
|
||||
Revealed = false;
|
||||
OnClose?.Invoke();
|
||||
}
|
||||
|
||||
public CUIFrame() : base()
|
||||
{
|
||||
CullChildren = true;
|
||||
Resizible = true;
|
||||
Draggable = true;
|
||||
}
|
||||
|
||||
public CUIFrame(float? x = null, float? y = null, float? w = null, float? h = null) : this()
|
||||
{
|
||||
Relative = new CUINullRect(x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// A Grid containing children in its cells
|
||||
/// </summary>
|
||||
public class CUIGrid : CUIComponent
|
||||
{
|
||||
public override CUILayout Layout
|
||||
{
|
||||
get => layout;
|
||||
set
|
||||
{
|
||||
layout = new CUILayoutGrid();
|
||||
layout.Host = this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CUILayoutGrid GridLayout => (CUILayoutGrid)Layout;
|
||||
|
||||
|
||||
|
||||
public CUIGrid() : base()
|
||||
{
|
||||
//Layout = new CUILayoutGrid();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Resizing components to it's Height and placing them sequentially
|
||||
/// </summary>
|
||||
public class CUIHorizontalList : CUIComponent
|
||||
{
|
||||
[CUISerializable] public bool Scrollable { get; set; }
|
||||
[CUISerializable] public float ScrollSpeed { get; set; } = 1.0f;
|
||||
|
||||
public float LeftGap = 0f;
|
||||
public float RightGap = 0f;
|
||||
|
||||
public override CUILayout Layout
|
||||
{
|
||||
get => layout;
|
||||
set
|
||||
{
|
||||
layout = new CUILayoutHorizontalList();
|
||||
layout.Host = this;
|
||||
}
|
||||
}
|
||||
public CUILayoutHorizontalList ListLayout => (CUILayoutHorizontalList)Layout;
|
||||
|
||||
[CUISerializable]
|
||||
public CUIDirection Direction
|
||||
{
|
||||
get => ListLayout.Direction;
|
||||
set => ListLayout.Direction = value;
|
||||
}
|
||||
|
||||
[CUISerializable]
|
||||
public bool ResizeToHostHeight
|
||||
{
|
||||
get => ListLayout.ResizeToHostHeight;
|
||||
set => ListLayout.ResizeToHostHeight = value;
|
||||
}
|
||||
|
||||
public float Scroll
|
||||
{
|
||||
get => ChildrenOffset.X;
|
||||
set
|
||||
{
|
||||
if (!Scrollable) return;
|
||||
CUIProps.ChildrenOffset.SetValue(
|
||||
ChildrenOffset with { X = value }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal override CUIBoundaries ChildOffsetBounds => new CUIBoundaries(
|
||||
minY: 0,
|
||||
maxY: 0,
|
||||
minX: LeftGap,
|
||||
maxX: Math.Min(Real.Width - ListLayout.TotalWidth - RightGap, 0)
|
||||
);
|
||||
public CUIHorizontalList() : base()
|
||||
{
|
||||
CullChildren = true;
|
||||
|
||||
|
||||
OnScroll += (m) => Scroll += m.Scroll * ScrollSpeed;
|
||||
ChildrenBoundaries = CUIBoundaries.HorizontalTube;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
#define SHOWPERF
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Orchestrating drawing and updating of it's children
|
||||
/// Also a CUIComponent, but it's draw and update methods
|
||||
/// Attached directly to games life cycle
|
||||
/// </summary>
|
||||
public class CUIMainComponent : CUIComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for global events
|
||||
/// </summary>
|
||||
public class CUIGlobalEvents
|
||||
{
|
||||
public Action<CUIInput> OnMouseDown; public void InvokeOnMouseDown(CUIInput e) => OnMouseDown?.Invoke(e);
|
||||
public Action<CUIInput> OnMouseUp; public void InvokeOnMouseUp(CUIInput e) => OnMouseUp?.Invoke(e);
|
||||
public Action<CUIInput> OnMouseMoved; public void InvokeOnMouseMoved(CUIInput e) => OnMouseMoved?.Invoke(e);
|
||||
public Action<CUIInput> OnClick; public void InvokeOnClick(CUIInput e) => OnClick?.Invoke(e);
|
||||
public Action<CUIInput> OnKeyDown; public void InvokeOnKeyDown(CUIInput e) => OnKeyDown?.Invoke(e);
|
||||
public Action<CUIInput> OnKeyUp; public void InvokeOnKeyUp(CUIInput e) => OnKeyUp?.Invoke(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frozen window doesn't update
|
||||
/// </summary>
|
||||
public bool Frozen { get; set; }
|
||||
public double UpdateInterval = 1.0 / 300.0;
|
||||
/// <summary>
|
||||
/// If true will update layout until it settles to prevent blinking
|
||||
/// </summary>
|
||||
public bool CalculateUntilResolved = true;
|
||||
/// <summary>
|
||||
/// If your GUI needs more than this steps of layout update
|
||||
/// you will get a warning
|
||||
/// </summary>
|
||||
public int MaxLayoutRecalcLoopsPerUpdate = 10;
|
||||
public event Action OnTreeChanged;
|
||||
public Action AddOnTreeChanged { set { OnTreeChanged += value; } }
|
||||
|
||||
public CUIDragHandle GrabbedDragHandle;
|
||||
public CUIResizeHandle GrabbedResizeHandle;
|
||||
public CUISwipeHandle GrabbedSwipeHandle;
|
||||
public CUIComponent MouseOn;
|
||||
public CUIComponent FocusedComponent
|
||||
{
|
||||
get => CUI.FocusedComponent;
|
||||
set => CUI.FocusedComponent = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Container for true global events
|
||||
/// CUIMainComponent itself can react to events and you can listen for those,
|
||||
/// but e.g. mouse events may be consumed before they reach Main
|
||||
/// </summary>
|
||||
public CUIGlobalEvents Global = new CUIGlobalEvents();
|
||||
|
||||
private Stopwatch sw = new Stopwatch();
|
||||
|
||||
internal List<CUIComponent> Flat = new List<CUIComponent>();
|
||||
internal List<CUIComponent> Leaves = new List<CUIComponent>();
|
||||
internal SortedList<int, List<CUIComponent>> Layers = new SortedList<int, List<CUIComponent>>();
|
||||
private List<CUIComponent> MouseOnList = new List<CUIComponent>();
|
||||
private Vector2 GrabbedOffset;
|
||||
|
||||
private void RunStraigth(Action<CUIComponent> a) { for (int i = 0; i < Flat.Count; i++) a(Flat[i]); }
|
||||
private void RunReverse(Action<CUIComponent> a) { for (int i = Flat.Count - 1; i >= 0; i--) a(Flat[i]); }
|
||||
|
||||
|
||||
|
||||
|
||||
private void FlattenTree()
|
||||
{
|
||||
int retries = 0;
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
retries++;
|
||||
if (retries > 10) break;
|
||||
try
|
||||
{
|
||||
Flat.Clear();
|
||||
Layers.Clear();
|
||||
|
||||
int globalIndex = 0;
|
||||
void CalcZIndexRec(CUIComponent component, int added = 0)
|
||||
{
|
||||
component.positionalZIndex = globalIndex;
|
||||
globalIndex += 1;
|
||||
component.addedZIndex = added;
|
||||
if (component.ZIndex.HasValue) component.addedZIndex += component.ZIndex.Value;
|
||||
|
||||
foreach (CUIComponent child in component.Children)
|
||||
{
|
||||
CalcZIndexRec(child, component.addedZIndex);
|
||||
}
|
||||
}
|
||||
|
||||
CalcZIndexRec(this, 0);
|
||||
RunRecursiveOn(this, (c) =>
|
||||
{
|
||||
int i = c.positionalZIndex + c.addedZIndex;
|
||||
if (!Layers.ContainsKey(i)) Layers[i] = new List<CUIComponent>();
|
||||
Layers[i].Add(c);
|
||||
});
|
||||
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
Flat.AddRange(layer.Value);
|
||||
}
|
||||
|
||||
done = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Couldn't Flatten component tree: {e.Message}");
|
||||
}
|
||||
} while (!done);
|
||||
}
|
||||
|
||||
#region Update
|
||||
|
||||
internal bool GlobalLayoutChanged;
|
||||
internal void LayoutChanged() => GlobalLayoutChanged = true;
|
||||
private double LastUpdateTime;
|
||||
private int UpdateLoopCount = 0;
|
||||
/// <summary>
|
||||
/// Forses 1 layout update step, even when Frozen
|
||||
/// </summary>
|
||||
public void Step()
|
||||
{
|
||||
Update(LastUpdateTime + UpdateInterval, true, true);
|
||||
}
|
||||
public void Update(double totalTime, bool force = false, bool noInput = false)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
if (Frozen) return;
|
||||
if (totalTime - LastUpdateTime <= UpdateInterval) return;
|
||||
}
|
||||
|
||||
CUIDebug.Flush();
|
||||
|
||||
if (TreeChanged)
|
||||
{
|
||||
OnTreeChanged?.Invoke();
|
||||
|
||||
FlattenTree();
|
||||
TreeChanged = false;
|
||||
}
|
||||
|
||||
if (!noInput) HandleInput(totalTime);
|
||||
|
||||
RunStraigth(c => c.InvokeOnUpdate(totalTime));
|
||||
|
||||
|
||||
if (CalculateUntilResolved)
|
||||
{
|
||||
UpdateLoopCount = 0;
|
||||
do
|
||||
{
|
||||
GlobalLayoutChanged = false;
|
||||
|
||||
if (TreeChanged)
|
||||
{
|
||||
OnTreeChanged?.Invoke();
|
||||
|
||||
FlattenTree();
|
||||
TreeChanged = false;
|
||||
}
|
||||
|
||||
RunReverse(c =>
|
||||
{
|
||||
c.Layout.ResizeToContent();
|
||||
});
|
||||
|
||||
RunStraigth(c =>
|
||||
{
|
||||
c.Layout.Update();
|
||||
c.Layout.UpdateDecor();
|
||||
});
|
||||
|
||||
UpdateLoopCount++;
|
||||
if (UpdateLoopCount >= MaxLayoutRecalcLoopsPerUpdate)
|
||||
{
|
||||
PrintRecalLimitWarning();
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (GlobalLayoutChanged);
|
||||
//CUI.Log($"UpdateLoopCount: {UpdateLoopCount}");
|
||||
}
|
||||
else
|
||||
{
|
||||
RunReverse(c =>
|
||||
{
|
||||
c.Layout.ResizeToContent();
|
||||
});
|
||||
|
||||
RunStraigth(c =>
|
||||
{
|
||||
c.Layout.Update();
|
||||
c.Layout.UpdateDecor();
|
||||
});
|
||||
}
|
||||
|
||||
//TODO do i need 2 updates?
|
||||
//RunStraigth(c => c.InvokeOnUpdate(totalTime));
|
||||
|
||||
LastUpdateTime = totalTime;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Draw
|
||||
|
||||
private void StopStart(SpriteBatch spriteBatch, Rectangle SRect, SamplerState? samplerState = null)
|
||||
{
|
||||
samplerState ??= GUI.SamplerState;
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = SRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: samplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
|
||||
public new void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
sw.Restart();
|
||||
|
||||
Rectangle OriginalSRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
Rectangle SRect = OriginalSRect;
|
||||
|
||||
try
|
||||
{
|
||||
RunStraigth(c =>
|
||||
{
|
||||
if (!c.Visible || c.CulledOut) return;
|
||||
if (c.Parent != null && c.Parent.ScissorRect.HasValue && SRect != c.Parent.ScissorRect.Value)
|
||||
{
|
||||
SRect = c.Parent.ScissorRect.Value;
|
||||
StopStart(spriteBatch, SRect, c.SamplerState);
|
||||
}
|
||||
c.Draw(spriteBatch);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (spriteBatch.GraphicsDevice.ScissorRectangle != OriginalSRect) StopStart(spriteBatch, OriginalSRect);
|
||||
}
|
||||
|
||||
RunStraigth(c =>
|
||||
{
|
||||
if (!c.Visible || c.CulledOut) return;
|
||||
c.DrawFront(spriteBatch);
|
||||
});
|
||||
|
||||
sw.Stop();
|
||||
// CUIDebug.EnsureCategory();
|
||||
// CUIDebug.CaptureTicks(sw.ElapsedTicks, "CUI.Draw");
|
||||
}
|
||||
#endregion
|
||||
// https://youtu.be/xuFgUmYCS8E?feature=shared&t=72
|
||||
#region HandleInput Start
|
||||
|
||||
public void OnDragEnd(CUIDragHandle h) { if (h == GrabbedDragHandle) GrabbedDragHandle = null; }
|
||||
public void OnResizeEnd(CUIResizeHandle h) { if (h == GrabbedResizeHandle) GrabbedResizeHandle = null; }
|
||||
public void OnSwipeEnd(CUISwipeHandle h) { if (h == GrabbedSwipeHandle) GrabbedSwipeHandle = null; }
|
||||
|
||||
|
||||
private void HandleInput(double totalTime)
|
||||
{
|
||||
HandleGlobal(totalTime);
|
||||
HandleMouse(totalTime);
|
||||
HandleKeyboard(totalTime);
|
||||
}
|
||||
|
||||
private void HandleGlobal(double totalTime)
|
||||
{
|
||||
if (CUI.Input.MouseDown) Global.InvokeOnMouseDown(CUI.Input);
|
||||
if (CUI.Input.MouseUp)
|
||||
{
|
||||
Global.InvokeOnMouseUp(CUI.Input);
|
||||
Global.InvokeOnClick(CUI.Input);
|
||||
}
|
||||
if (CUI.Input.MouseMoved) Global.InvokeOnMouseMoved(CUI.Input);
|
||||
if (CUI.Input.SomeKeyPressed) Global.InvokeOnKeyDown(CUI.Input);
|
||||
if (CUI.Input.SomeKeyUnpressed) Global.InvokeOnKeyUp(CUI.Input);
|
||||
}
|
||||
|
||||
private void HandleKeyboard(double totalTime)
|
||||
{
|
||||
if (FocusedComponent == null) FocusedComponent = this;
|
||||
if (CUI.Input.PressedKeys.Contains(Keys.Escape)) FocusedComponent = this;
|
||||
if (CUI.Input.SomeKeyPressed) FocusedComponent.InvokeOnKeyDown(CUI.Input);
|
||||
if (CUI.Input.SomeKeyUnpressed) FocusedComponent.InvokeOnKeyUp(CUI.Input);
|
||||
if (CUI.Input.SomeWindowEvents) FocusedComponent.InvokeOnTextInput(CUI.Input);
|
||||
}
|
||||
|
||||
private void HandleMouse(double totalTime)
|
||||
{
|
||||
if (!CUI.Input.SomethingHappened) return;
|
||||
|
||||
if (!CUI.Input.MouseHeld)
|
||||
{
|
||||
GrabbedDragHandle?.EndDrag();
|
||||
GrabbedResizeHandle?.EndResize();
|
||||
GrabbedSwipeHandle?.EndSwipe();
|
||||
}
|
||||
|
||||
if (CUI.Input.MouseMoved)
|
||||
{
|
||||
GrabbedDragHandle?.DragTo(CUI.Input.MousePosition);
|
||||
GrabbedResizeHandle?.Resize(CUI.Input.MousePosition);
|
||||
GrabbedSwipeHandle?.Swipe(CUI.Input);
|
||||
}
|
||||
|
||||
if (CUI.Input.MouseInputHandled) return;
|
||||
|
||||
//HACK
|
||||
//if (CUI.Input.ClickConsumed) return;
|
||||
|
||||
//TODO think where should i put it?
|
||||
if (GrabbedResizeHandle != null || GrabbedDragHandle != null || GrabbedSwipeHandle != null) return;
|
||||
|
||||
List<CUIComponent> prevMouseOnList = new List<CUIComponent>(MouseOnList);
|
||||
|
||||
CUIComponent CurrentMouseOn = null;
|
||||
MouseOnList.Clear();
|
||||
|
||||
|
||||
|
||||
// form MouseOnList
|
||||
// Note: including main component
|
||||
if (
|
||||
GUI.MouseOn == null || (GUI.MouseOn is GUIButton btn && btn.Text == "DUMMY")
|
||||
|| (this == CUI.TopMain) //TODO guh
|
||||
)
|
||||
{
|
||||
RunStraigth(c =>
|
||||
{
|
||||
bool ok = !c.IgnoreEvents && c.Real.Contains(CUI.Input.MousePosition) && c.ShouldInvoke(CUI.Input);
|
||||
|
||||
if (c.Parent != null && c.Parent.ScissorRect.HasValue &&
|
||||
!c.Parent.ScissorRect.Value.Contains(CUI.Input.Mouse.Position))
|
||||
{
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ok) MouseOnList.Add(c);
|
||||
});
|
||||
}
|
||||
|
||||
MouseOn = MouseOnList.LastOrDefault();
|
||||
|
||||
//HACK
|
||||
if (MouseOn != this)
|
||||
{
|
||||
CUI.Input.MouseInputHandled = true;
|
||||
CUIMultiModResolver.MarkOtherInputsAsHandled();
|
||||
}
|
||||
|
||||
//if (CurrentMouseOn != null) GUI.MouseOn = dummyComponent;
|
||||
|
||||
|
||||
foreach (CUIComponent c in prevMouseOnList)
|
||||
{
|
||||
c.MousePressed = false;
|
||||
c.MouseOver = false;
|
||||
c.InvokeOnMouseOff(CUI.Input);
|
||||
}
|
||||
|
||||
foreach (CUIComponent c in MouseOnList)
|
||||
{
|
||||
c.MousePressed = CUI.Input.MouseHeld;
|
||||
c.MouseOver = true;
|
||||
c.InvokeOnMouseOn(CUI.Input);
|
||||
}
|
||||
|
||||
// Mouse enter / leave
|
||||
foreach (CUIComponent c in prevMouseOnList.Except(MouseOnList)) c.InvokeOnMouseLeave(CUI.Input);
|
||||
foreach (CUIComponent c in MouseOnList.Except(prevMouseOnList)) c.InvokeOnMouseEnter(CUI.Input);
|
||||
|
||||
|
||||
// focus
|
||||
if (CUI.Input.MouseDown)
|
||||
{
|
||||
CUIComponent newFocused = this;
|
||||
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MouseOnList[i].FocusHandle.ShouldStart(CUI.Input))
|
||||
{
|
||||
newFocused = MouseOnList[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
FocusedComponent = newFocused;
|
||||
}
|
||||
|
||||
// Resize
|
||||
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MouseOnList[i].RightResizeHandle.ShouldStart(CUI.Input))
|
||||
{
|
||||
GrabbedResizeHandle = MouseOnList[i].RightResizeHandle;
|
||||
GrabbedResizeHandle.BeginResize(CUI.Input.MousePosition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (MouseOnList[i].LeftResizeHandle.ShouldStart(CUI.Input))
|
||||
{
|
||||
GrabbedResizeHandle = MouseOnList[i].LeftResizeHandle;
|
||||
GrabbedResizeHandle.BeginResize(CUI.Input.MousePosition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (GrabbedResizeHandle != null) return;
|
||||
|
||||
//Scroll
|
||||
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (CUI.Input.Scrolled) MouseOnList[i].InvokeOnScroll(CUI.Input);
|
||||
|
||||
if (MouseOnList[i].ConsumeMouseScroll) break;
|
||||
}
|
||||
|
||||
//Move
|
||||
if (CUI.Input.MouseMoved)
|
||||
{
|
||||
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
MouseOnList[i].InvokeOnMouseMove(CUI.Input);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Clicks
|
||||
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (CUI.Input.MouseDown) MouseOnList[i].InvokeOnMouseDown(CUI.Input);
|
||||
if (CUI.Input.MouseUp)
|
||||
{
|
||||
MouseOnList[i].InvokeOnMouseUp(CUI.Input);
|
||||
MouseOnList[i].InvokeOnClick(CUI.Input);
|
||||
}
|
||||
if (CUI.Input.DoubleClick) MouseOnList[i].InvokeOnDClick(CUI.Input);
|
||||
|
||||
if (MouseOnList[i].ConsumeMouseClicks || CUI.Input.ClickConsumed) break;
|
||||
}
|
||||
if (CUI.Input.ClickConsumed) return;
|
||||
|
||||
// Swipe
|
||||
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MouseOnList[i].SwipeHandle.ShouldStart(CUI.Input))
|
||||
{
|
||||
GrabbedSwipeHandle = MouseOnList[i].SwipeHandle;
|
||||
GrabbedSwipeHandle.BeginSwipe(CUI.Input.MousePosition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (MouseOnList[i].ConsumeSwipe) break;
|
||||
}
|
||||
if (GrabbedSwipeHandle != null) return;
|
||||
|
||||
// Drag
|
||||
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MouseOnList[i].DragHandle.ShouldStart(CUI.Input))
|
||||
{
|
||||
GrabbedDragHandle = MouseOnList[i].DragHandle;
|
||||
GrabbedDragHandle.BeginDrag(CUI.Input.MousePosition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (MouseOnList[i].ConsumeDragAndDrop) break;
|
||||
}
|
||||
if (GrabbedDragHandle != null) return;
|
||||
}
|
||||
#endregion
|
||||
#region HandleInput End
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete function
|
||||
/// Will run generator func with this
|
||||
/// </summary>
|
||||
/// <param name="initFunc"> Generator function that adds components to passed Main </param>
|
||||
public void Load(Action<CUIMainComponent> initFunc)
|
||||
{
|
||||
RemoveAllChildren();
|
||||
initFunc(this);
|
||||
}
|
||||
|
||||
public CUIMainComponent() : base()
|
||||
{
|
||||
CullChildren = true;
|
||||
Real = new CUIRect(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
Visible = false;
|
||||
//IgnoreEvents = true;
|
||||
ShouldPassPropsToChildren = false;
|
||||
|
||||
|
||||
Debug = true;
|
||||
ChildrenBoundaries = CUIBoundaries.Box;
|
||||
}
|
||||
|
||||
public void PrintRecalLimitWarning()
|
||||
{
|
||||
CUI.Log($"Warning: Your GUI code requires {MaxLayoutRecalcLoopsPerUpdate} layout update loops to fully resolve (which is cringe). Optimize it!", Color.Orange);
|
||||
}
|
||||
}
|
||||
}
|
||||
244
Quick Interactions/CSharp/Client/CrabUI/Components/CUIMap.cs
Normal file
244
Quick Interactions/CSharp/Client/CrabUI/Components/CUIMap.cs
Normal file
@@ -0,0 +1,244 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Swipable and zoomable plane
|
||||
/// Allows you to place components in a plane
|
||||
/// and connect them with lines like a graph or scheme
|
||||
/// </summary>
|
||||
public class CUIMap : CUIComponent
|
||||
{
|
||||
#region CUIMapLink
|
||||
#endregion
|
||||
public class CUIMapLink
|
||||
{
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () => Default = new CUIMapLink(null, null);
|
||||
CUI.OnDispose += () => Default = null;
|
||||
}
|
||||
public static CUIMapLink Default;
|
||||
|
||||
public CUIComponent Start;
|
||||
public CUIComponent End;
|
||||
|
||||
//TODO all this crap wasn't designed for nested AKA
|
||||
public string StartAKA;
|
||||
public string EndAKA;
|
||||
public float LineWidth;
|
||||
public Color LineColor;
|
||||
|
||||
public XElement ToXML()
|
||||
{
|
||||
XElement connection = new XElement("Connection");
|
||||
if (LineWidth != Default.LineWidth)
|
||||
{
|
||||
connection.SetAttributeValue("LineWidth", LineWidth);
|
||||
}
|
||||
connection.SetAttributeValue("Start", StartAKA ?? "");
|
||||
connection.SetAttributeValue("End", EndAKA ?? "");
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
public CUIMapLink(CUIComponent start, CUIComponent end, Color? lineColor = null, float lineWidth = 2f)
|
||||
{
|
||||
LineColor = lineColor ?? new Color(128, 128, 128);
|
||||
LineWidth = lineWidth;
|
||||
Start = start;
|
||||
End = end;
|
||||
|
||||
StartAKA = start?.AKA;
|
||||
EndAKA = end?.AKA;
|
||||
}
|
||||
}
|
||||
|
||||
#region LinksContainer
|
||||
#endregion
|
||||
public class LinksContainer : CUIComponent
|
||||
{
|
||||
public List<CUIMapLink> Connections = new List<CUIMapLink>();
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
foreach (CUIMapLink link in Connections)
|
||||
{
|
||||
Vector2 midPoint = new Vector2(link.End.Real.Center.X, link.Start.Real.Center.Y);
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
link.Start.Real.Center,
|
||||
midPoint,
|
||||
link.LineColor, width: link.LineWidth
|
||||
);
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
midPoint,
|
||||
link.End.Real.Center,
|
||||
link.LineColor, width: link.LineWidth
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public LinksContainer()
|
||||
{
|
||||
UnCullable = true;
|
||||
BackgroundColor = Color.Transparent;
|
||||
Border.Color = Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
#region CUIMap
|
||||
#endregion
|
||||
|
||||
public LinksContainer linksContainer;
|
||||
public List<CUIMapLink> Connections => linksContainer.Connections;
|
||||
|
||||
public CUIComponent Add(CUIComponent c) => Append(c, c.AKA);
|
||||
|
||||
|
||||
|
||||
public CUIComponent Connect(CUIComponent startComponent, CUIComponent endComponent, Color? color = null)
|
||||
{
|
||||
if (startComponent != null && endComponent != null)
|
||||
{
|
||||
if (color == null && (!startComponent.Disabled || !endComponent.Disabled)) color = new Color(0, 0, 255);
|
||||
linksContainer.Connections.Add(new CUIMapLink(startComponent, endComponent, color));
|
||||
}
|
||||
return startComponent;
|
||||
}
|
||||
public CUIComponent Connect(CUIComponent startComponent, int end = -2, Color? color = null)
|
||||
{
|
||||
end = MathUtils.PositiveModulo(end, Children.Count);
|
||||
CUIComponent endComponent = Children.ElementAtOrDefault(end);
|
||||
return Connect(startComponent, endComponent, color);
|
||||
}
|
||||
|
||||
//TODO DRY
|
||||
public CUIComponent Connect(string start, string end, Color? color = null)
|
||||
{
|
||||
CUIComponent startComponent = this[start];
|
||||
CUIComponent endComponent = this[end];
|
||||
|
||||
if (startComponent != null && endComponent != null)
|
||||
{
|
||||
if (color == null && (!startComponent.Disabled || !endComponent.Disabled)) color = new Color(0, 0, 255);
|
||||
linksContainer.Connections.Add(new CUIMapLink(startComponent, endComponent, color)
|
||||
{
|
||||
StartAKA = start,
|
||||
EndAKA = end,
|
||||
});
|
||||
}
|
||||
return startComponent;
|
||||
}
|
||||
public CUIComponent Connect(int start, int end, Color? color = null)
|
||||
{
|
||||
start = MathUtils.PositiveModulo(start, Children.Count);
|
||||
end = MathUtils.PositiveModulo(end, Children.Count);
|
||||
|
||||
CUIComponent startComponent = Children.ElementAtOrDefault(start);
|
||||
CUIComponent endComponent = Children.ElementAtOrDefault(end);
|
||||
return Connect(startComponent, endComponent, color);
|
||||
}
|
||||
|
||||
public CUIComponent ConnectTo(CUIComponent Host, params CUIComponent[] children)
|
||||
{
|
||||
foreach (CUIComponent child in children) { Connect(Host, child); }
|
||||
return Host;
|
||||
}
|
||||
|
||||
|
||||
public override XElement ToXML(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
Type type = GetType();
|
||||
|
||||
XElement element = new XElement(type.Name);
|
||||
|
||||
PackProps(element, propAttribute);
|
||||
|
||||
XElement connections = new XElement("Connections");
|
||||
element.Add(connections);
|
||||
|
||||
foreach (CUIMapLink link in Connections)
|
||||
{
|
||||
connections.Add(link.ToXML());
|
||||
}
|
||||
|
||||
XElement children = new XElement("Children");
|
||||
element.Add(children);
|
||||
|
||||
foreach (CUIComponent child in Children)
|
||||
{
|
||||
if (child == linksContainer) continue;
|
||||
children.Add(child.ToXML());
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
public override void FromXML(XElement element, string baseFolder = null)
|
||||
{
|
||||
foreach (XElement childElement in element.Element("Children").Elements())
|
||||
{
|
||||
Type childType = CUIReflection.GetComponentTypeByName(childElement.Name.ToString());
|
||||
if (childType == null) continue;
|
||||
|
||||
CUIComponent child = (CUIComponent)Activator.CreateInstance(childType);
|
||||
child.FromXML(childElement);
|
||||
|
||||
this.Append(child, child.AKA);
|
||||
}
|
||||
|
||||
foreach (XElement link in element.Element("Connections").Elements())
|
||||
{
|
||||
CUIComponent startComponent = this[link.Attribute("Start").Value];
|
||||
CUIComponent endComponent = this[link.Attribute("End").Value];
|
||||
|
||||
if (startComponent == null || endComponent == null)
|
||||
{
|
||||
CUIDebug.Error("startComponent == null || endComponent == null");
|
||||
continue;
|
||||
}
|
||||
Connect(link.Attribute("Start").Value, link.Attribute("End").Value);
|
||||
}
|
||||
|
||||
//TODO: think, this is potentially very bugged,
|
||||
// Some props might need to be assigned before children, and some after
|
||||
ExtractProps(element);
|
||||
}
|
||||
|
||||
public CUIMap() : base()
|
||||
{
|
||||
Swipeable = true;
|
||||
ConsumeMouseClicks = true;
|
||||
CullChildren = true;
|
||||
BackgroundColor = Color.Transparent;
|
||||
|
||||
//without container links won't be culled
|
||||
//TODO linksContainer should be special and not just first child
|
||||
this["links"] = linksContainer = new LinksContainer();
|
||||
|
||||
OnScroll += (m) =>
|
||||
{
|
||||
CUIProps.ChildrenOffset.SetValue(
|
||||
ChildrenOffset.Zoom(
|
||||
m.MousePosition - Real.Position,
|
||||
(-m.Scroll / 500f)
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Button with multiple options
|
||||
/// which are rotating when you click
|
||||
/// </summary>
|
||||
public class CUIMultiButton : CUIButton
|
||||
{
|
||||
private List<string> options = new List<string>();
|
||||
/// <summary>
|
||||
/// Options are just strings
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public IEnumerable<string> Options
|
||||
{
|
||||
get => options;
|
||||
set => options = value.ToList();
|
||||
}
|
||||
public event Action<string> OnSelect;
|
||||
public Action<string> AddOnSelect { set { OnSelect += value; } }
|
||||
|
||||
public bool CycleOnClick { get; set; } = true;
|
||||
public int SelectedIndex
|
||||
{
|
||||
get => options.IndexOf(Selected);
|
||||
set
|
||||
{
|
||||
if (options.Count == 0)
|
||||
{
|
||||
Selected = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
Selected = options.ElementAtOrDefault(value % options.Count) ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
[CUISerializable]
|
||||
public string Selected
|
||||
{
|
||||
get => Text;
|
||||
set
|
||||
{
|
||||
Text = value;
|
||||
OnSelect?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string option) => options.Add(option);
|
||||
public void Remove(string option)
|
||||
{
|
||||
int i = options.IndexOf(option);
|
||||
options.Remove(option);
|
||||
if (option == Selected) Select(i);
|
||||
}
|
||||
public void Select(int i) => SelectedIndex = i;
|
||||
public void Select(string option) => Selected = option;
|
||||
public void SelectNext() => SelectedIndex++;
|
||||
public void SelectPrev() => SelectedIndex--;
|
||||
|
||||
public CUIMultiButton() : base()
|
||||
{
|
||||
Text = "MultiButton";
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
if (CycleOnClick)
|
||||
{
|
||||
SelectNext();
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, Selected));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// CUITextBlock DoWrapFor but for all text
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <returns></returns>
|
||||
protected override Vector2 DoWrapFor(Vector2 size)
|
||||
{
|
||||
if ((!WrappedForThisSize.HasValue || size == WrappedForThisSize.Value) && !TextPropChanged) return WrappedSize;
|
||||
|
||||
TextPropChanged = false;
|
||||
WrappedForThisSize = size;
|
||||
|
||||
if (Vertical) size = new Vector2(0, size.Y);
|
||||
|
||||
|
||||
IEnumerable<string> WrappedTexts;
|
||||
if (Wrap || Vertical)
|
||||
{
|
||||
WrappedText = Font.WrapText(Text, size.X / TextScale - Padding.X * 2).Trim('\n');
|
||||
WrappedTexts = options.Select(o => Font.WrapText(o, size.X / TextScale - Padding.X * 2).Trim('\n'));
|
||||
}
|
||||
else
|
||||
{
|
||||
WrappedText = Text;
|
||||
WrappedTexts = options;
|
||||
}
|
||||
|
||||
IEnumerable<Vector2> RealTextSizes = WrappedTexts.Select(t => Font.MeasureString(t) * TextScale);
|
||||
|
||||
float maxX = 0;
|
||||
float maxY = 0;
|
||||
foreach (Vector2 s in RealTextSizes)
|
||||
{
|
||||
if (s.X > maxX) maxX = s.X;
|
||||
if (s.Y > maxY) maxY = s.Y;
|
||||
}
|
||||
|
||||
Vector2 MaxTextSize = new Vector2(maxX, maxY);
|
||||
|
||||
RealTextSize = Font.MeasureString(WrappedText) * TextScale;
|
||||
|
||||
if (WrappedText == "") RealTextSize = new Vector2(0, 0);
|
||||
RealTextSize = new Vector2((float)Math.Round(RealTextSize.X), (float)Math.Round(RealTextSize.Y));
|
||||
|
||||
Vector2 minSize = MaxTextSize + Padding * 2;
|
||||
|
||||
if (!Wrap || Vertical)
|
||||
{
|
||||
SetForcedMinSize(new CUINullVector2(minSize));
|
||||
}
|
||||
|
||||
WrappedSize = new Vector2(Math.Max(size.X, minSize.X), Math.Max(size.Y, minSize.Y));
|
||||
|
||||
return WrappedSize;
|
||||
}
|
||||
|
||||
internal override Vector2 AmIOkWithThisSize(Vector2 size)
|
||||
{
|
||||
return DoWrapFor(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Container for other components
|
||||
/// Can have only 1 child
|
||||
/// Sets component as it's only child when you open it (as a page)
|
||||
/// </summary>
|
||||
public class CUIPages : CUIComponent
|
||||
{
|
||||
public CUIComponent OpenedPage;
|
||||
|
||||
public bool IsOpened(CUIComponent p) => OpenedPage == p;
|
||||
|
||||
/// <summary>
|
||||
/// Adds page as its only child
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
public void Open(CUIComponent page)
|
||||
{
|
||||
RemoveAllChildren();
|
||||
Append(page);
|
||||
page.Relative = new CUINullRect(0, 0, 1, 1);
|
||||
OpenedPage = page;
|
||||
}
|
||||
|
||||
public CUIPages() : base()
|
||||
{
|
||||
BackgroundColor = Color.Transparent;
|
||||
Border.Color = Color.Transparent;
|
||||
CullChildren = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml.Linq;
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Passive block of text
|
||||
/// </summary>
|
||||
public class CUITextBlock : CUIComponent
|
||||
{
|
||||
public event Action OnTextChanged;
|
||||
public Action AddOnTextChanged { set { OnTextChanged += value; } }
|
||||
|
||||
|
||||
//TODO move padding here, it makes no sense in CUIComponent
|
||||
private bool wrap;
|
||||
[CUISerializable]
|
||||
public bool Wrap
|
||||
{
|
||||
get => wrap;
|
||||
set
|
||||
{
|
||||
wrap = value;
|
||||
MeasureUnwrapped();
|
||||
TextPropChanged = true;
|
||||
}
|
||||
}
|
||||
[CUISerializable] public Color TextColor { get; set; }
|
||||
private GUIFont font = GUIStyle.Font;
|
||||
[CUISerializable]
|
||||
public GUIFont Font
|
||||
{
|
||||
get => font;
|
||||
set
|
||||
{
|
||||
font = value;
|
||||
MeasureUnwrapped();
|
||||
TextPropChanged = true;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// A Vector2 ([0..1],[0..1])
|
||||
/// </summary>
|
||||
[CUISerializable] public Vector2 TextAlign { get; set; }
|
||||
[CUISerializable] public bool Vertical { get; set; }
|
||||
/// <summary>
|
||||
/// Lil optimization: ghost text won't set forsed size and parent won't be able to fit to it
|
||||
/// But it will increase performance in large lists
|
||||
/// </summary>
|
||||
[CUISerializable] public bool GhostText { get; set; }
|
||||
|
||||
[CUISerializable]
|
||||
public string Text { get => text; set => SetText(value); }
|
||||
[CUISerializable]
|
||||
public float TextScale { get => textScale; set => SetTextScale(value); }
|
||||
|
||||
#region Cringe
|
||||
protected Vector2 RealTextSize;
|
||||
[Calculated] protected Vector2 TextDrawPos { get; set; }
|
||||
[Calculated] protected string WrappedText { get; set; } = "";
|
||||
protected Vector2? WrappedForThisSize;
|
||||
[Calculated] protected Vector2 WrappedSize { get; set; }
|
||||
public Vector2 UnwrappedTextSize { get; set; }
|
||||
public Vector2 UnwrappedMinSize { get; set; }
|
||||
protected bool TextPropChanged;
|
||||
#endregion
|
||||
|
||||
protected string text = ""; internal void SetText(string value)
|
||||
{
|
||||
text = value ?? "";
|
||||
OnTextChanged?.Invoke();
|
||||
|
||||
MeasureUnwrapped();
|
||||
TextPropChanged = true;
|
||||
OnPropChanged();
|
||||
OnAbsolutePropChanged();
|
||||
}
|
||||
|
||||
protected float textScale = 0.9f; internal void SetTextScale(float value)
|
||||
{
|
||||
textScale = value;
|
||||
MeasureUnwrapped();
|
||||
TextPropChanged = true;
|
||||
OnPropChanged();
|
||||
OnAbsolutePropChanged();
|
||||
}
|
||||
|
||||
//Note: works only on unwrapped text for now because WrappedText is delayed
|
||||
/// <summary>
|
||||
/// X coordinate of caret if there was one
|
||||
/// Used in CUITextInput, you don't need this
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <returns></returns>
|
||||
public float CaretPos(int i)
|
||||
{
|
||||
return Font.MeasureString(Text.SubstringSafe(0, i)).X * TextScale + Padding.X;
|
||||
}
|
||||
|
||||
//Note: works only on unwrapped text for now because WrappedText is delayed
|
||||
/// <summary>
|
||||
/// Tndex of caret if there was one
|
||||
/// Used in CUITextInput, you don't need this
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <returns></returns>
|
||||
public int CaretIndex(float x)
|
||||
{
|
||||
int Aprox = (int)Math.Round((x - Padding.X) / Font.MeasureString(Text).X * Text.Length);
|
||||
|
||||
int closestCaretPos = Aprox;
|
||||
float smallestDif = Math.Abs(x - CaretPos(Aprox));
|
||||
|
||||
for (int i = Aprox - 2; i <= Aprox + 2; i++)
|
||||
{
|
||||
float dif = Math.Abs(x - CaretPos(i));
|
||||
if (dif < smallestDif)
|
||||
{
|
||||
closestCaretPos = i;
|
||||
smallestDif = dif;
|
||||
}
|
||||
}
|
||||
|
||||
return closestCaretPos;
|
||||
}
|
||||
|
||||
// Small optimisation, doesn't seem to save much
|
||||
protected virtual void MeasureUnwrapped()
|
||||
{
|
||||
UnwrappedTextSize = Font.MeasureString(Text) * TextScale;
|
||||
UnwrappedMinSize = UnwrappedTextSize + Padding * 2;
|
||||
}
|
||||
|
||||
protected virtual Vector2 DoWrapFor(Vector2 size)
|
||||
{
|
||||
// To prevent loop
|
||||
if (!(WrappedForThisSize.HasValue && WrappedForThisSize != size) && !TextPropChanged) return WrappedSize;
|
||||
|
||||
TextPropChanged = false;
|
||||
WrappedForThisSize = size;
|
||||
|
||||
// There's no way to wrap vertical text
|
||||
bool isInWrapZone = Vertical ? false : size.X <= UnwrappedMinSize.X;
|
||||
bool isSolid = Vertical || !Wrap;
|
||||
|
||||
if (Vertical) size = new Vector2(0, size.Y);
|
||||
|
||||
if ((Wrap && isInWrapZone) || Vertical)
|
||||
{
|
||||
WrappedText = Font.WrapText(Text, size.X / TextScale - Padding.X * 2).Trim('\n');
|
||||
RealTextSize = Font.MeasureString(WrappedText) * TextScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
WrappedText = Text;
|
||||
RealTextSize = UnwrappedTextSize;
|
||||
}
|
||||
|
||||
if (WrappedText == "") RealTextSize = new Vector2(0, 0);
|
||||
|
||||
RealTextSize = new Vector2((float)Math.Round(RealTextSize.X), (float)Math.Round(RealTextSize.Y));
|
||||
|
||||
Vector2 minSize = RealTextSize + Padding * 2;
|
||||
|
||||
if (isSolid && !GhostText)
|
||||
{
|
||||
SetForcedMinSize(new CUINullVector2(minSize));
|
||||
}
|
||||
|
||||
WrappedSize = new Vector2(Math.Max(size.X, minSize.X), Math.Max(size.Y, minSize.Y));
|
||||
|
||||
return WrappedSize;
|
||||
}
|
||||
|
||||
internal override Vector2 AmIOkWithThisSize(Vector2 size)
|
||||
{
|
||||
return DoWrapFor(size);
|
||||
}
|
||||
|
||||
//Note: This is a bottleneck for large lists of text
|
||||
internal override void UpdatePseudoChildren()
|
||||
{
|
||||
if (CulledOut) return;
|
||||
TextDrawPos = CUIAnchor.GetChildPos(Real, TextAlign, Vector2.Zero, RealTextSize / Scale) + Padding * CUIAnchor.Direction(TextAlign) / Scale;
|
||||
|
||||
//CUIDebug.Capture(null, this, "UpdatePseudoChildren", "", "TextDrawPos", $"{TextDrawPos - Real.Position}");
|
||||
}
|
||||
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
// Font.DrawString(spriteBatch, WrappedText, TextDrawPos, TextColor, rotation: 0, origin: Vector2.Zero, TextScale, spriteEffects: SpriteEffects.None, layerDepth: 0.1f);
|
||||
|
||||
Font.Value.DrawString(spriteBatch, WrappedText, TextDrawPos, TextColor, rotation: 0, origin: Vector2.Zero, TextScale / Scale, se: SpriteEffects.None, layerDepth: 0.1f);
|
||||
}
|
||||
|
||||
public CUITextBlock() { }
|
||||
|
||||
public CUITextBlock(string text) : this()
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using EventInput;
|
||||
using System.Windows;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Text input
|
||||
/// </summary>
|
||||
public class CUITextInput : CUIComponent, IKeyboardSubscriber
|
||||
{
|
||||
|
||||
#region IKeyboardSubscriber
|
||||
|
||||
private Keys pressedKey;
|
||||
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
try
|
||||
{
|
||||
pressedKey = key;
|
||||
if (key == Keys.Back) Back();
|
||||
if (key == Keys.Delete) Delete();
|
||||
if (key == Keys.Left) MoveLeft();
|
||||
if (key == Keys.Right) MoveRight();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveTextInput(char inputChar) => ReceiveTextInput(inputChar.ToString());
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveTextInput(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
CutSelection();
|
||||
Text = Text.Insert(CaretPos, text);
|
||||
CaretPos = CaretPos + 1;
|
||||
OnTextAdded?.Invoke(text);
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
//CUI.Log($"ReceiveTextInput {text}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Log(e);
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveCommandInput(char command)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pressedKey == Keys.A) SelectAll();
|
||||
if (pressedKey == Keys.C) Copy();
|
||||
if (pressedKey == Keys.V) Paste();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
}
|
||||
//CUI.Log($"ReceiveCommandInput {command}");
|
||||
}
|
||||
|
||||
//Alt+tab?
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveEditingInput(string text, int start, int length)
|
||||
{
|
||||
//CUI.Log($"ReceiveEditingInput {text} {start} {length}");
|
||||
}
|
||||
|
||||
//TODO mb should lose focus here
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public bool Selected { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
public void SelectAll() => Select(0, Text.Length);
|
||||
|
||||
public void Copy()
|
||||
{
|
||||
if (Selection.IsEmpty) return;
|
||||
selectionHandle.Grabbed = false;
|
||||
Clipboard.SetText(Text.SubstringSafe(Selection.Start, Selection.End));
|
||||
}
|
||||
public void Paste()
|
||||
{
|
||||
ReceiveTextInput(Clipboard.GetText());
|
||||
}
|
||||
|
||||
public void AddText(string text) => ReceiveTextInput(text);
|
||||
public void MoveLeft()
|
||||
{
|
||||
CaretPos--;
|
||||
Selection = IntRange.Zero;
|
||||
}
|
||||
public void MoveRight()
|
||||
{
|
||||
CaretPos++;
|
||||
Selection = IntRange.Zero;
|
||||
}
|
||||
|
||||
// //TODO
|
||||
// public void SelectLeft()
|
||||
// {
|
||||
// if (Selection == IntRange.Zero) Selection = new IntRange(CaretPos - 1, CaretPos);
|
||||
// else Selection = new IntRange(Selection.Start - 1, Selection.End);
|
||||
// }
|
||||
// //TODO
|
||||
// public void SelectRight()
|
||||
// {
|
||||
// if (Selection == IntRange.Zero) Selection = new IntRange(CaretPos, CaretPos + 1);
|
||||
// else Selection = new IntRange(Selection.Start, Selection.End + 1);
|
||||
// }
|
||||
|
||||
public void Back()
|
||||
{
|
||||
TextInputState oldState = State;
|
||||
if (!Selection.IsEmpty) CutSelection();
|
||||
else
|
||||
{
|
||||
string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos - 1);
|
||||
string s2 = oldState.Text.SubstringSafe(oldState.CaretPos);
|
||||
Text = s1 + s2;
|
||||
CaretPos = oldState.CaretPos - 1;
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
TextInputState oldState = State;
|
||||
if (!Selection.IsEmpty) CutSelection();
|
||||
else
|
||||
{
|
||||
string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos);
|
||||
string s2 = oldState.Text.SubstringSafe(oldState.CaretPos + 1);
|
||||
Text = s1 + s2;
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
//CaretPos = oldState.CaretPos;
|
||||
}
|
||||
}
|
||||
|
||||
public void CutSelection()
|
||||
{
|
||||
if (Selection.IsEmpty) return;
|
||||
selectionHandle.Grabbed = false;
|
||||
string s1 = Text.SubstringSafe(0, Selection.Start);
|
||||
string s2 = Text.SubstringSafe(Selection.End);
|
||||
Text = s1 + s2;
|
||||
CaretPos = Selection.Start;
|
||||
Selection = IntRange.Zero;
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
}
|
||||
|
||||
internal int SetCaretPos(Vector2 v)
|
||||
{
|
||||
int newCaretPos = TextComponent.CaretIndex(v.X);
|
||||
CaretPos = newCaretPos;
|
||||
return newCaretPos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal class SelectionHandle
|
||||
{
|
||||
public bool Grabbed;
|
||||
public int lastSelectedPos;
|
||||
}
|
||||
internal SelectionHandle selectionHandle = new SelectionHandle();
|
||||
|
||||
internal record struct TextInputState(string Text, IntRange Selection, int CaretPos)
|
||||
{
|
||||
public string Text { get; init; } = Text ?? "";
|
||||
}
|
||||
private TextInputState state; internal TextInputState State
|
||||
{
|
||||
get => state;
|
||||
set
|
||||
{
|
||||
state = ValidateState(value);
|
||||
ApplyState(state);
|
||||
}
|
||||
}
|
||||
|
||||
internal TextInputState ValidateState(TextInputState state)
|
||||
{
|
||||
//return state with { CaretPos = state.CaretPos.Fit(0, state.Text.Length - 1) };
|
||||
|
||||
string newText = state.Text;
|
||||
|
||||
IntRange newSelection = new IntRange(
|
||||
state.Selection.Start.Fit(0, newText.Length),
|
||||
state.Selection.End.Fit(0, newText.Length)
|
||||
);
|
||||
|
||||
int newCaretPos = state.CaretPos.Fit(0, newText.Length);
|
||||
|
||||
return new TextInputState(newText, newSelection, newCaretPos);
|
||||
}
|
||||
|
||||
internal void ApplyState(TextInputState state)
|
||||
{
|
||||
TextComponent.Text = state.Text;
|
||||
|
||||
SelectionOverlay.Visible = !state.Selection.IsEmpty;
|
||||
CaretIndicatorVisible = Focused && !SelectionOverlay.Visible;
|
||||
|
||||
if (!state.Selection.IsEmpty)
|
||||
{
|
||||
SelectionOverlay.Absolute = SelectionOverlay.Absolute with
|
||||
{
|
||||
Left = TextComponent.CaretPos(state.Selection.Start),
|
||||
Width = TextComponent.CaretPos(state.Selection.End) - TextComponent.CaretPos(state.Selection.Start),
|
||||
};
|
||||
}
|
||||
|
||||
CaretIndicator.Absolute = CaretIndicator.Absolute with
|
||||
{
|
||||
Left = TextComponent.CaretPos(state.CaretPos),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool valid = true; public bool Valid
|
||||
{
|
||||
get => valid;
|
||||
set
|
||||
{
|
||||
if (valid == value) return;
|
||||
valid = value;
|
||||
UpdateBorderColor();
|
||||
}
|
||||
}
|
||||
|
||||
public Type VatidationType { get; set; }
|
||||
public bool IsValidText(string text)
|
||||
{
|
||||
if (VatidationType == null) return true;
|
||||
|
||||
if (VatidationType == typeof(string)) return true;
|
||||
if (VatidationType == typeof(Color)) return true;
|
||||
if (VatidationType == typeof(bool)) return bool.TryParse(text, out _);
|
||||
if (VatidationType == typeof(int)) return int.TryParse(text, out _);
|
||||
if (VatidationType == typeof(float)) return float.TryParse(text, out _);
|
||||
if (VatidationType == typeof(double)) return double.TryParse(text, out _);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO this is cringe
|
||||
// public override void Consume(object o)
|
||||
// {
|
||||
// string value = (string)o;
|
||||
// State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||
// Valid = IsValidText(value);
|
||||
// }
|
||||
|
||||
internal CUITextBlock TextComponent;
|
||||
public string Text
|
||||
{
|
||||
get => State.Text;
|
||||
set
|
||||
{
|
||||
if (Disabled) return;
|
||||
|
||||
State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||
|
||||
bool isvalid = IsValidText(value);
|
||||
if (isvalid)
|
||||
{
|
||||
OnTextChanged?.Invoke(State.Text);
|
||||
}
|
||||
Valid = isvalid;
|
||||
}
|
||||
}
|
||||
|
||||
internal CUIComponent SelectionOverlay;
|
||||
public IntRange Selection
|
||||
{
|
||||
get => State.Selection;
|
||||
set => State = new TextInputState(State.Text, value, State.CaretPos);
|
||||
}
|
||||
public void Select(int start, int end) => Selection = new IntRange(start, end);
|
||||
|
||||
|
||||
public bool CaretIndicatorVisible { get; set; }
|
||||
public double CaretBlinkInterval { get; set; } = 0.5;
|
||||
internal CUIComponent CaretIndicator;
|
||||
public int CaretPos
|
||||
{
|
||||
get => State.CaretPos;
|
||||
set => State = new TextInputState(State.Text, State.Selection, value);
|
||||
}
|
||||
|
||||
|
||||
//TODO
|
||||
//[CUISerializable] public bool PreventOverflow { get; set; } = false;
|
||||
|
||||
public void UpdateBorderColor()
|
||||
{
|
||||
if (Valid)
|
||||
{
|
||||
if (Focused)
|
||||
{
|
||||
Style["Border"] = "CUIPalette.Input.Focused";
|
||||
}
|
||||
else
|
||||
{
|
||||
Style["Border"] = "CUIPalette.Input.Border";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Style["Border"] = "CUIPalette.Input.Invalid";
|
||||
}
|
||||
}
|
||||
[CUISerializable]
|
||||
public float TextScale
|
||||
{
|
||||
get => TextComponent?.TextScale ?? 0;
|
||||
set => TextComponent.TextScale = value;
|
||||
}
|
||||
public Color TextColor
|
||||
{
|
||||
get => TextComponent?.TextColor ?? Color.White;
|
||||
set
|
||||
{
|
||||
if (TextComponent != null)
|
||||
{
|
||||
TextComponent.TextColor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<string> OnTextChanged;
|
||||
public Action<string> AddOnTextChanged { set => OnTextChanged += value; }
|
||||
public event Action<string> OnTextAdded;
|
||||
public Action<string> AddOnTextAdded { set => OnTextAdded += value; }
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Focused)
|
||||
{
|
||||
CaretIndicator.Visible = CaretIndicatorVisible && Timing.TotalTime % CaretBlinkInterval > CaretBlinkInterval / 2;
|
||||
}
|
||||
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CUITextInput(string text) : this()
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
public CUITextInput() : base()
|
||||
{
|
||||
AbsoluteMin = new CUINullRect(w: 50, h: 22);
|
||||
FitContent = new CUIBool2(true, true);
|
||||
Focusable = true;
|
||||
Border.Thickness = 2;
|
||||
HideChildrenOutsideFrame = true;
|
||||
ConsumeMouseClicks = true;
|
||||
ConsumeDragAndDrop = true;
|
||||
ConsumeSwipe = true;
|
||||
BreakSerialization = true;
|
||||
|
||||
this["TextComponent"] = TextComponent = new CUITextBlock()
|
||||
{
|
||||
Text = "",
|
||||
Relative = new CUINullRect(0, 0, 1, 1),
|
||||
TextAlign = CUIAnchor.CenterLeft,
|
||||
Style = new CUIStyle(){
|
||||
{"Padding", "[2,2]"},
|
||||
{"TextColor", "CUIPalette.Input.Text"},
|
||||
},
|
||||
};
|
||||
|
||||
this["SelectionOverlay"] = SelectionOverlay = new CUIComponent()
|
||||
{
|
||||
Style = new CUIStyle(){
|
||||
{"BackgroundColor", "CUIPalette.Input.Selection"},
|
||||
{"Border", "Transparent"},
|
||||
},
|
||||
Relative = new CUINullRect(h: 1),
|
||||
Ghost = new CUIBool2(true, true),
|
||||
IgnoreParentVisibility = true,
|
||||
};
|
||||
|
||||
this["CaretIndicator"] = CaretIndicator = new CUIComponent()
|
||||
{
|
||||
Style = new CUIStyle(){
|
||||
{"BackgroundColor", "CUIPalette.Input.Caret"},
|
||||
{"Border", "Transparent"},
|
||||
},
|
||||
Relative = new CUINullRect(y: 0.1f, h: 0.8f),
|
||||
Absolute = new CUINullRect(w: 1),
|
||||
Ghost = new CUIBool2(true, true),
|
||||
Visible = false,
|
||||
IgnoreParentVisibility = true,
|
||||
};
|
||||
|
||||
OnConsume += (o) =>
|
||||
{
|
||||
string value = o.ToString();
|
||||
State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||
Valid = IsValidText(value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
OnFocus += () =>
|
||||
{
|
||||
UpdateBorderColor();
|
||||
CaretIndicator.Visible = true;
|
||||
};
|
||||
|
||||
OnFocusLost += () =>
|
||||
{
|
||||
UpdateBorderColor();
|
||||
Selection = IntRange.Zero;
|
||||
CaretIndicator.Visible = false;
|
||||
};
|
||||
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
int newCaretPos = SetCaretPos(e.MousePosition - Real.Position);
|
||||
Selection = IntRange.Zero;
|
||||
selectionHandle.lastSelectedPos = newCaretPos;
|
||||
selectionHandle.Grabbed = true;
|
||||
};
|
||||
|
||||
OnMouseMove += (e) =>
|
||||
{
|
||||
if (selectionHandle.Grabbed)
|
||||
{
|
||||
int nextCaretPos = SetCaretPos(e.MousePosition - Real.Position);
|
||||
Selection = new IntRange(selectionHandle.lastSelectedPos, nextCaretPos);
|
||||
}
|
||||
};
|
||||
|
||||
OnDClick += (e) => SelectAll();
|
||||
|
||||
if (CUI.Main is not null)
|
||||
{
|
||||
CUI.Main.Global.OnMouseUp += (e) => selectionHandle.Grabbed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Extensions;
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// A button which can be on and off
|
||||
/// It's derived from CUITextBlock and has all its props
|
||||
/// </summary>
|
||||
public class CUIToggleButton : CUITextBlock
|
||||
{
|
||||
[CUISerializable]
|
||||
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
|
||||
|
||||
[CUISerializable] public Color DisabledColor { get; set; }
|
||||
[CUISerializable] public Color OnColor { get; set; }
|
||||
[CUISerializable] public Color OnHoverColor { get; set; }
|
||||
[CUISerializable] public Color OffColor { get; set; }
|
||||
[CUISerializable] public Color OffHoverColor { get; set; }
|
||||
|
||||
public Color MasterColor
|
||||
{
|
||||
set
|
||||
{
|
||||
OffColor = value.Multiply(0.5f);
|
||||
OffHoverColor = value;
|
||||
OnColor = value.Multiply(0.9f);
|
||||
OnHoverColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Color MasterColorOpaque
|
||||
{
|
||||
set
|
||||
{
|
||||
OffColor = new Color((int)(value.R * 0.5f), (int)(value.G * 0.5f), (int)(value.B * 0.5f), value.A);
|
||||
OffHoverColor = value;
|
||||
OnColor = new Color((int)(value.R * 0.9f), (int)(value.G * 0.9f), (int)(value.B * 0.9f), value.A); ;
|
||||
OnHoverColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
// BackgroundColor is used in base.Draw, but here it's calculated from OnColor/OffColor
|
||||
// so it's not a prop anymore, and i don't want to serialize it
|
||||
public new Color BackgroundColor { get => CUIProps.BackgroundColor.Value; set => CUIProps.BackgroundColor.SetValue(value); }
|
||||
|
||||
|
||||
private string onText;
|
||||
private string offText;
|
||||
[CUISerializable]
|
||||
public string OnText
|
||||
{
|
||||
get => onText;
|
||||
set { onText = value; if (State && onText != null) Text = onText; }
|
||||
}
|
||||
|
||||
[CUISerializable]
|
||||
public string OffText
|
||||
{
|
||||
get => offText;
|
||||
set { offText = value; if (!State && offText != null) Text = offText; }
|
||||
}
|
||||
|
||||
public event Action<bool> OnStateChange;
|
||||
public Action<bool> AddOnStateChange { set { OnStateChange += value; } }
|
||||
|
||||
|
||||
protected bool state;
|
||||
[CUISerializable]
|
||||
public bool State
|
||||
{
|
||||
get => state;
|
||||
set
|
||||
{
|
||||
state = value;
|
||||
if (state && OnText != null) Text = OnText;
|
||||
if (!state && OffText != null) Text = OffText;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
BackgroundColor = DisabledColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (State)
|
||||
{
|
||||
if (MouseOver) BackgroundColor = OnHoverColor;
|
||||
else BackgroundColor = OnColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MouseOver) BackgroundColor = OffHoverColor;
|
||||
else BackgroundColor = OffColor;
|
||||
}
|
||||
}
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
public CUIToggleButton() : base()
|
||||
{
|
||||
ConsumeMouseClicks = true;
|
||||
ConsumeDragAndDrop = true;
|
||||
ConsumeSwipe = true;
|
||||
|
||||
//BackgroundColor = OffColor;
|
||||
|
||||
TextAlign = new Vector2(0.5f, 0.5f);
|
||||
Padding = new Vector2(4, 2);
|
||||
|
||||
Text = nameof(CUIToggleButton);
|
||||
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
if (!Disabled)
|
||||
{
|
||||
State = !State;
|
||||
SoundPlayer.PlayUISound(ClickSound);
|
||||
OnStateChange?.Invoke(State);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public CUIToggleButton(string text) : this()
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Resizing components to it's Width and placing them sequentially
|
||||
/// </summary>
|
||||
public class CUIVerticalList : CUIComponent
|
||||
{
|
||||
[CUISerializable] public bool Scrollable { get; set; }
|
||||
[CUISerializable] public float ScrollSpeed { get; set; } = 1.0f;
|
||||
|
||||
[CUISerializable] public float TopGap { get; set; } = 0;
|
||||
[CUISerializable] public float BottomGap { get; set; } = 10f;
|
||||
|
||||
public override CUILayout Layout
|
||||
{
|
||||
get => layout;
|
||||
set
|
||||
{
|
||||
layout = new CUILayoutVerticalList();
|
||||
layout.Host = this;
|
||||
}
|
||||
}
|
||||
public CUILayoutVerticalList ListLayout => (CUILayoutVerticalList)Layout;
|
||||
|
||||
|
||||
[CUISerializable]
|
||||
public CUIDirection Direction
|
||||
{
|
||||
get => ListLayout.Direction;
|
||||
set => ListLayout.Direction = value;
|
||||
}
|
||||
|
||||
//TODO test, sync with hlist
|
||||
[CUISerializable]
|
||||
public float Gap
|
||||
{
|
||||
get => ListLayout.Gap;
|
||||
set => ListLayout.Gap = value;
|
||||
}
|
||||
|
||||
[CUISerializable]
|
||||
public bool ResizeToHostWidth
|
||||
{
|
||||
get => ListLayout.ResizeToHostWidth;
|
||||
set => ListLayout.ResizeToHostWidth = value;
|
||||
}
|
||||
|
||||
public float Scroll
|
||||
{
|
||||
get => ChildrenOffset.Y;
|
||||
set
|
||||
{
|
||||
if (!Scrollable) return;
|
||||
CUIProps.ChildrenOffset.SetValue(
|
||||
ChildrenOffset with { Y = value }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal override CUIBoundaries ChildOffsetBounds => new CUIBoundaries(
|
||||
minX: 0,
|
||||
maxX: 0,
|
||||
maxY: TopGap,
|
||||
minY: Math.Min(Real.Height - ListLayout.TotalHeight - BottomGap, 0)
|
||||
);
|
||||
|
||||
|
||||
public CUIVerticalList() : base()
|
||||
{
|
||||
CullChildren = true;
|
||||
|
||||
|
||||
OnScroll += (m) => Scroll += m.Scroll * ScrollSpeed;
|
||||
|
||||
ChildrenBoundaries = CUIBoundaries.VerticalTube;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple dialog box with a message and ok button
|
||||
/// use public static void Open(string msg) to open it
|
||||
/// </summary>
|
||||
public class CUIMessageBox : CUIFrame
|
||||
{
|
||||
public static void Open(string msg)
|
||||
{
|
||||
CUI.TopMain.Append(new CUIMessageBox(msg));
|
||||
}
|
||||
|
||||
|
||||
public CUIMessageBox(string msg) : base()
|
||||
{
|
||||
Palette = PaletteOrder.Quaternary;
|
||||
Resizible = false;
|
||||
|
||||
Relative = new CUINullRect(0, 0, 0.25f, 0.25f);
|
||||
Anchor = CUIAnchor.Center;
|
||||
|
||||
OutlineThickness = 2;
|
||||
|
||||
this["layout"] = new CUIVerticalList()
|
||||
{
|
||||
Relative = new CUINullRect(0, 0, 1, 1),
|
||||
};
|
||||
|
||||
this["layout"]["main"] = new CUIVerticalList()
|
||||
{
|
||||
FillEmptySpace = new CUIBool2(false, true),
|
||||
Scrollable = true,
|
||||
ScrollSpeed = 0.5f,
|
||||
Style = CUIStylePrefab.Main,
|
||||
};
|
||||
|
||||
this["layout"]["main"]["msg"] = new CUITextBlock(msg)
|
||||
{
|
||||
TextScale = 1.2f,
|
||||
Wrap = true,
|
||||
Font = GUIStyle.Font,
|
||||
TextAlign = CUIAnchor.TopCenter,
|
||||
Style = new CUIStyle()
|
||||
{
|
||||
["Padding"] = "[10,10]",
|
||||
},
|
||||
};
|
||||
this["layout"]["ok"] = new CUIButton("Ok")
|
||||
{
|
||||
TextScale = 1.0f,
|
||||
Style = new CUIStyle()
|
||||
{
|
||||
["Padding"] = "[10,10]",
|
||||
},
|
||||
AddOnMouseDown = (e) => this.RemoveSelf(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
namespace QICrabUI
|
||||
{
|
||||
// hmm, idk if this should be a prefab or component
|
||||
// it's too small for component
|
||||
// but in prefab i can't use initializer
|
||||
public class CUICloseButton : CUIButton
|
||||
{
|
||||
public CUICloseButton() : base()
|
||||
{
|
||||
Command = "Close";
|
||||
Text = "";
|
||||
ZIndex = 10;
|
||||
BackgroundSprite = CUI.TextureManager.GetCUISprite(3, 1);
|
||||
Absolute = new CUINullRect(0, 0, 15, 15);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// It's a debug tool, you can use it with cuimg command, it's very fps comsuming
|
||||
/// </summary>
|
||||
[NoDefault]
|
||||
public class CUIMagnifyingGlass : CUICanvas
|
||||
{
|
||||
|
||||
|
||||
public static CUIFrame GlassFrame;
|
||||
|
||||
public static void AddToggleButton()
|
||||
{
|
||||
CUI.TopMain["ToggleMagnifyingGlass"] = new CUIButton("MG")
|
||||
{
|
||||
Absolute = new CUINullRect(0, 0, 20, 20),
|
||||
Anchor = CUIAnchor.CenterLeft,
|
||||
AddOnMouseDown = (e) => ToggleEquip(),
|
||||
};
|
||||
}
|
||||
|
||||
public static void ToggleEquip()
|
||||
{
|
||||
if (GlassFrame != null)
|
||||
{
|
||||
GlassFrame.RemoveSelf();
|
||||
GlassFrame = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
GlassFrame = new CUIFrame()
|
||||
{
|
||||
ZIndex = 100000,
|
||||
|
||||
BackgroundColor = Color.Transparent,
|
||||
Border = new CUIBorder(Color.Cyan, 5),
|
||||
Anchor = CUIAnchor.Center,
|
||||
Absolute = new CUINullRect(w: 200, h: 200),
|
||||
};
|
||||
GlassFrame["glass"] = new CUIMagnifyingGlass();
|
||||
CUI.TopMain["MagnifyingGlass"] = GlassFrame;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CleanUp()
|
||||
{
|
||||
texture.Dispose();
|
||||
base.CleanUp();
|
||||
}
|
||||
Texture2D texture;
|
||||
Color[] backBuffer;
|
||||
|
||||
|
||||
double lastDrawn;
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Timing.TotalTime - lastDrawn > 0.05)
|
||||
{
|
||||
lastDrawn = Timing.TotalTime;
|
||||
|
||||
GameMain.Instance.GraphicsDevice.GetBackBufferData<Color>(backBuffer);
|
||||
texture.SetData(backBuffer);
|
||||
|
||||
texture.GetData<Color>(
|
||||
0, new Rectangle((int)Real.Left, (int)Real.Top, 40, 40), Data, 0, Data.Length
|
||||
);
|
||||
SetData();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
public CUIMagnifyingGlass() : base()
|
||||
{
|
||||
|
||||
|
||||
Size = new Point(40, 40);
|
||||
SamplerState = CUI.NoSmoothing;
|
||||
Relative = new CUINullRect(0, 0, 1, 1);
|
||||
|
||||
int w = GameMain.Instance.GraphicsDevice.PresentationParameters.BackBufferWidth;
|
||||
int h = GameMain.Instance.GraphicsDevice.PresentationParameters.BackBufferHeight;
|
||||
|
||||
backBuffer = new Color[w * h];
|
||||
|
||||
texture = new Texture2D(GameMain.Instance.GraphicsDevice, w, h, false, GameMain.Instance.GraphicsDevice.PresentationParameters.BackBufferFormat);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using EventInput;
|
||||
using System.Windows;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
//TODO move all this to defauld styles
|
||||
/// <summary>
|
||||
/// CUITextBlock adapted for CUIMenu
|
||||
/// </summary>
|
||||
public class CUIMenuText : CUITextBlock
|
||||
{
|
||||
public CUIMenuText(string text) : this() => Text = text;
|
||||
public CUIMenuText() : base()
|
||||
{
|
||||
Anchor = CUIAnchor.Center;
|
||||
TextScale = 1.0f;
|
||||
ZIndex = 100;
|
||||
TextColor = Color.Black;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Component with a sprite that will notify parent CUIMenu when clicked
|
||||
/// </summary>
|
||||
public class CUIMenuOption : CUIComponent
|
||||
{
|
||||
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
|
||||
/// <summary>
|
||||
/// This is the Value that will be send to CUIMenu on click, and will be passed to OnSelect event
|
||||
/// </summary>
|
||||
[CUISerializable] public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normal background color
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public Color BaseColor
|
||||
{
|
||||
get => (Color)Animations["hover"].StartValue;
|
||||
set
|
||||
{
|
||||
Animations["hover"].StartValue = value;
|
||||
Animations["hover"].ApplyValue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Background color when hovered
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public Color HoverColor
|
||||
{
|
||||
get => (Color)Animations["hover"].EndValue;
|
||||
set => Animations["hover"].EndValue = value;
|
||||
}
|
||||
|
||||
|
||||
public CUIMenuOption()
|
||||
{
|
||||
BackgroundColor = new Color(255, 255, 255, 255);
|
||||
Relative = new CUINullRect(0, 0, 1, 1);
|
||||
|
||||
IgnoreTransparent = true;
|
||||
Command = "CUIMenuOption select";
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
SoundPlayer.PlayUISound(ClickSound);
|
||||
DispatchUp(new CUICommand(Command, Value));
|
||||
};
|
||||
|
||||
Animations["hover"] = new CUIAnimation()
|
||||
{
|
||||
StartValue = new Color(255, 255, 255, 255),
|
||||
EndValue = new Color(255, 255, 255, 255),
|
||||
Duration = 0.1,
|
||||
ReverseDuration = 0.3,
|
||||
Property = "BackgroundColor",
|
||||
};
|
||||
|
||||
OnMouseEnter += (e) => Animations["hover"].Forward();
|
||||
OnMouseLeave += (e) => Animations["hover"].Back();
|
||||
}
|
||||
}
|
||||
|
||||
public class CUIMenu : CUIComponent, IKeyboardSubscriber
|
||||
{
|
||||
// this allows it to intercept esc key press naturally,
|
||||
// but it also blocks normal hotkey bindings, so idk
|
||||
// ----------------- IKeyboardSubscriber -----------------
|
||||
public void ReceiveSpecialInput(Keys key) { if (key == Keys.Escape) Close(); }
|
||||
public void ReceiveTextInput(char inputChar) => ReceiveTextInput(inputChar.ToString());
|
||||
public void ReceiveTextInput(string text) { }
|
||||
public void ReceiveCommandInput(char command) { }
|
||||
public void ReceiveEditingInput(string text, int start, int length) { }
|
||||
public bool Selected { get; set; }
|
||||
// ----------------- IKeyboardSubscriber -----------------
|
||||
|
||||
public static void InitStatic() => CUI.OnDispose += () => Menus.Clear();
|
||||
/// <summary>
|
||||
/// All loaded menus are stored here by Name
|
||||
/// </summary>
|
||||
public static Dictionary<string, CUIMenu> Menus = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initial fade in animation duration, set to 0 to disable
|
||||
/// </summary>
|
||||
[CUISerializable]
|
||||
public double FadeInDuration
|
||||
{
|
||||
get => Animations["fade"].Duration;
|
||||
set => Animations["fade"].Duration = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will be used as key for this menu in CUIMenu.Menus
|
||||
/// </summary>
|
||||
[CUISerializable] public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Does nothing, just a prop so you could get author programmatically
|
||||
/// </summary>
|
||||
[CUISerializable] public string Author { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true will act as IKeyboardSubscriber. Don't
|
||||
/// </summary>
|
||||
[CUISerializable] public bool BlockInput { get; set; }
|
||||
/// <summary>
|
||||
/// Happens when some CUIMenuOption is clicked, the value of that option is passed to it
|
||||
/// </summary>
|
||||
public event Action<string> OnSelect;
|
||||
/// <summary>
|
||||
/// Will add this as a child to host (CUI.Main) and start fadein animation
|
||||
/// </summary>
|
||||
public void Open(CUIComponent host = null)
|
||||
{
|
||||
if (Parent != null) return;
|
||||
host ??= CUI.Main;
|
||||
host.Append(this);
|
||||
|
||||
if (BlockInput) CUI.FocusedComponent = this;
|
||||
|
||||
Animations["fade"].SetToStart();
|
||||
Animations["fade"].Forward();
|
||||
}
|
||||
|
||||
public void Close() => RemoveSelf();
|
||||
|
||||
public void Toggle(CUIComponent host = null)
|
||||
{
|
||||
if (Parent != null) Close();
|
||||
else Open(host);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads CUIMenu from a file to CUIMenu.Menus
|
||||
/// </summary>
|
||||
public static CUIMenu Load(string path)
|
||||
{
|
||||
CUIMenu menu = CUIComponent.LoadFromFile<CUIMenu>(path);
|
||||
if (menu == null) CUI.Warning($"Couldn't load CUIMenu from {path}");
|
||||
if (menu?.Name != null) Menus[menu.Name] = menu;
|
||||
return menu;
|
||||
}
|
||||
|
||||
public CUIMenu() : base()
|
||||
{
|
||||
BackgroundColor = new Color(255, 255, 255, 255);
|
||||
Anchor = CUIAnchor.Center;
|
||||
Transparency = 0.0f;
|
||||
|
||||
AddCommand("CUIMenuOption select", (o) =>
|
||||
{
|
||||
if (o is string s) OnSelect?.Invoke(s);
|
||||
//Close();
|
||||
});
|
||||
|
||||
Animations["fade"] = new CUIAnimation()
|
||||
{
|
||||
StartValue = 0.0f,
|
||||
EndValue = 1.0f,
|
||||
Duration = 0.2,
|
||||
Property = "Transparency",
|
||||
};
|
||||
|
||||
if (CUI.Main != null)
|
||||
{
|
||||
CUI.Main.Global.OnKeyDown += (e) =>
|
||||
{
|
||||
if (e.PressedKeys.Contains(Keys.Escape)) Close();
|
||||
};
|
||||
|
||||
CUI.Main.OnMouseDown += (e) => Close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Unfinished crap, don't use
|
||||
/// </summary>
|
||||
public class CUIRadialMenuOption : CUIComponent
|
||||
{
|
||||
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
|
||||
|
||||
public Color BaseColor
|
||||
{
|
||||
get => (Color)this.Animations["hover"].StartValue;
|
||||
set => this.Animations["hover"].StartValue = value;
|
||||
}
|
||||
|
||||
public Color Hover
|
||||
{
|
||||
get => (Color)this.Animations["hover"].EndValue;
|
||||
set => this.Animations["hover"].EndValue = value;
|
||||
}
|
||||
|
||||
public float TextRadius { get; set; } = 0.4f;
|
||||
|
||||
public void SetRotation(float angle)
|
||||
{
|
||||
BackgroundSprite.Offset = new Vector2(0.5f, 0.5f);
|
||||
BackgroundSprite.Rotation = angle;
|
||||
|
||||
|
||||
this["Text"].Relative = new CUINullRect(
|
||||
(float)(TextRadius * Math.Cos(angle - Math.PI / 2)),
|
||||
(float)(TextRadius * Math.Sin(angle - Math.PI / 2))
|
||||
);
|
||||
}
|
||||
|
||||
public Action Callback;
|
||||
|
||||
public CUIRadialMenuOption(string name = "", Action callback = null)
|
||||
{
|
||||
IgnoreTransparent = true;
|
||||
Relative = new CUINullRect(0, 0, 1, 1);
|
||||
|
||||
Callback = callback;
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
SoundPlayer.PlayUISound(ClickSound);
|
||||
Callback?.Invoke();
|
||||
};
|
||||
|
||||
this.Animations["hover"] = new CUIAnimation()
|
||||
{
|
||||
StartValue = new Color(255, 255, 255, 255),
|
||||
EndValue = new Color(0, 255, 255, 255),
|
||||
Property = "BackgroundColor",
|
||||
Duration = 0.2,
|
||||
ReverseDuration = 0.3,
|
||||
};
|
||||
|
||||
this.Animations["hover"].ApplyValue();
|
||||
|
||||
OnMouseEnter += (e) => Animations["hover"].Forward();
|
||||
OnMouseLeave += (e) => Animations["hover"].Back();
|
||||
|
||||
this["Text"] = new CUITextBlock(name)
|
||||
{
|
||||
Anchor = CUIAnchor.Center,
|
||||
ZIndex = 100,
|
||||
TextScale = 1.0f,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unfinished crap, don't use
|
||||
/// </summary>
|
||||
public class CUIRadialMenu : CUIComponent
|
||||
{
|
||||
public CUIRadialMenuOption OptionTemplate = new();
|
||||
|
||||
public Dictionary<string, CUIRadialMenuOption> Options = new();
|
||||
|
||||
public CUIRadialMenuOption AddOption(string name, Action callback)
|
||||
{
|
||||
CUIRadialMenuOption option = new CUIRadialMenuOption(name, callback);
|
||||
option.ApplyState(OptionTemplate);
|
||||
option.Animations["hover"].Interpolate = OptionTemplate.Animations["hover"].Interpolate;
|
||||
option.Animations["hover"].ApplyValue();
|
||||
|
||||
this[name] = Options[name] = option;
|
||||
option.OnClick += (e) => Close();
|
||||
|
||||
CalculateRotations();
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
public void CalculateRotations()
|
||||
{
|
||||
float delta = (float)(Math.PI * 2 / Options.Count);
|
||||
|
||||
int i = 0;
|
||||
foreach (CUIRadialMenuOption option in Options.Values)
|
||||
{
|
||||
option.SetRotation(delta * i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpened => Parent != null;
|
||||
|
||||
public void Open(CUIComponent host = null)
|
||||
{
|
||||
host ??= CUI.Main;
|
||||
host.Append(this);
|
||||
Animations["fade"].SetToStart();
|
||||
Animations["fade"].Forward();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
// BlockChildrenAnimations();
|
||||
// Animations["fade"].SetToEnd();
|
||||
// Animations["fade"].Back();
|
||||
RemoveSelf();
|
||||
}
|
||||
|
||||
|
||||
public CUIRadialMenu() : base()
|
||||
{
|
||||
Anchor = CUIAnchor.Center;
|
||||
Relative = new CUINullRect(h: 0.8f);
|
||||
CrossRelative = new CUINullRect(w: 0.8f);
|
||||
BackgroundColor = new Color(255, 255, 255, 255);
|
||||
//BackgroundSprite = new CUISprite("RadialMenu.png");
|
||||
|
||||
Animations["fade"] = new CUIAnimation()
|
||||
{
|
||||
StartValue = 0.0f,
|
||||
EndValue = 1.0f,
|
||||
Property = "Transparency",
|
||||
Duration = 0.2,
|
||||
ReverseDuration = 0.1,
|
||||
};
|
||||
|
||||
//HACK
|
||||
Animations["fade"].OnStop += (dir) =>
|
||||
{
|
||||
if (dir == CUIDirection.Reverse)
|
||||
{
|
||||
RemoveSelf();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Horizontal range input
|
||||
/// </summary>
|
||||
public class CUISlider : CUIComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Happens when handle is dragged, value is [0..1]
|
||||
/// </summary>
|
||||
public event Action<float> OnSlide;
|
||||
public Action<float> AddOnSlide { set { OnSlide += value; } }
|
||||
public float InOutMult => (Real.Width - Real.Height) / Real.Width;
|
||||
|
||||
private float lambda;
|
||||
private float? pendingLambda;
|
||||
public float Lambda
|
||||
{
|
||||
get => lambda;
|
||||
set
|
||||
{
|
||||
lambda = Math.Clamp(value, 0, 1);
|
||||
pendingLambda = lambda;
|
||||
}
|
||||
}
|
||||
|
||||
[CUISerializable] public FloatRange Range { get; set; } = new FloatRange(0, 1);
|
||||
[CUISerializable] public int? Precision { get; set; } = 2;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The handle
|
||||
/// </summary>
|
||||
public CUIComponent Handle;
|
||||
|
||||
public CUIComponent LeftEnding;
|
||||
public CUIComponent RightEnding;
|
||||
public CUISprite MiddleSprite;
|
||||
|
||||
public CUIRect MiddleRect;
|
||||
|
||||
public Color MasterColor
|
||||
{
|
||||
set
|
||||
{
|
||||
if (LeftEnding != null) LeftEnding.BackgroundColor = value;
|
||||
if (RightEnding != null) RightEnding.BackgroundColor = value;
|
||||
if (Handle != null) Handle.BackgroundColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
base.Draw(spriteBatch);
|
||||
CUI.DrawRectangle(spriteBatch, MiddleRect, LeftEnding.BackgroundColor, MiddleSprite);
|
||||
}
|
||||
|
||||
public CUISlider() : base()
|
||||
{
|
||||
ChildrenBoundaries = CUIBoundaries.Box;
|
||||
BreakSerialization = true;
|
||||
|
||||
this["LeftEnding"] = LeftEnding = new CUIComponent()
|
||||
{
|
||||
Anchor = CUIAnchor.CenterLeft,
|
||||
Relative = new CUINullRect(h: 1),
|
||||
CrossRelative = new CUINullRect(w: 1),
|
||||
BackgroundSprite = CUI.TextureManager.GetCUISprite(2, 2, CUISpriteDrawMode.Resize, SpriteEffects.FlipHorizontally),
|
||||
Style = new CUIStyle()
|
||||
{
|
||||
["Border"] = "Transparent",
|
||||
["BackgroundColor"] = "CUIPalette.Slider",
|
||||
},
|
||||
};
|
||||
|
||||
this["RightEnding"] = RightEnding = new CUIComponent()
|
||||
{
|
||||
Anchor = CUIAnchor.CenterRight,
|
||||
Relative = new CUINullRect(h: 1),
|
||||
CrossRelative = new CUINullRect(w: 1),
|
||||
BackgroundSprite = CUI.TextureManager.GetCUISprite(2, 2),
|
||||
Style = new CUIStyle()
|
||||
{
|
||||
["Border"] = "Transparent",
|
||||
["BackgroundColor"] = "CUIPalette.Slider",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
this["handle"] = Handle = new CUIComponent()
|
||||
{
|
||||
Style = new CUIStyle()
|
||||
{
|
||||
["Border"] = "Transparent",
|
||||
["BackgroundColor"] = "CUIPalette.Slider",
|
||||
},
|
||||
Draggable = true,
|
||||
BackgroundSprite = CUI.TextureManager.GetCUISprite(0, 2),
|
||||
Relative = new CUINullRect(h: 1),
|
||||
CrossRelative = new CUINullRect(w: 1),
|
||||
AddOnDrag = (x, y) =>
|
||||
{
|
||||
lambda = Math.Clamp(x / InOutMult, 0, 1);
|
||||
OnSlide?.Invoke(lambda);
|
||||
if (Command != null)
|
||||
{
|
||||
float value = Range.PosOf(lambda);
|
||||
if (Precision.HasValue) value = (float)Math.Round(value, Precision.Value);
|
||||
DispatchUp(new CUICommand(Command, value));
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
Handle.DragHandle.DragRelative = true;
|
||||
|
||||
MiddleSprite = CUI.TextureManager.GetSprite("CUI.png", new Rectangle(44, 64, 6, 32));
|
||||
|
||||
OnLayoutUpdated += () =>
|
||||
{
|
||||
MiddleRect = new CUIRect(
|
||||
Real.Left + Real.Height,
|
||||
Real.Top,
|
||||
Real.Width - 2 * Real.Height,
|
||||
Real.Height
|
||||
);
|
||||
|
||||
if (pendingLambda.HasValue)
|
||||
{
|
||||
Handle.Relative = Handle.Relative with
|
||||
{
|
||||
Left = Math.Clamp(pendingLambda.Value, 0, 1) * InOutMult,
|
||||
};
|
||||
pendingLambda = null;
|
||||
}
|
||||
};
|
||||
|
||||
OnConsume += (o) =>
|
||||
{
|
||||
if (float.TryParse(o.ToString(), out float value))
|
||||
{
|
||||
Lambda = Range.LambdaOf(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Just a tick box
|
||||
/// </summary>
|
||||
public class CUITickBox : CUIComponent
|
||||
{
|
||||
public GUISoundType ClickSound { get; set; } = GUISoundType.TickBox;
|
||||
|
||||
public event Action<bool> OnStateChange;
|
||||
public void AddOnStateChange(Action<bool> callback) => OnStateChange += callback;
|
||||
|
||||
public CUISprite OnSprite { get; set; }
|
||||
public CUISprite OffSprite { get; set; }
|
||||
public CUISprite HoverOffSprite { get; set; }
|
||||
public CUISprite HoverOnSprite { get; set; }
|
||||
public CUISprite DisabledSprite { get; set; }
|
||||
|
||||
private bool state; public bool State
|
||||
{
|
||||
get => state;
|
||||
set
|
||||
{
|
||||
if (state == value) return;
|
||||
state = value;
|
||||
ChangeSprite();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Disabled
|
||||
{
|
||||
get => disabled;
|
||||
set
|
||||
{
|
||||
disabled = value;
|
||||
ChangeSprite();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ChangeSprite()
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
BackgroundSprite = DisabledSprite;
|
||||
return;
|
||||
}
|
||||
|
||||
if (State)
|
||||
{
|
||||
BackgroundSprite = OnSprite;
|
||||
if (MouseOver) BackgroundSprite = HoverOnSprite;
|
||||
}
|
||||
else
|
||||
{
|
||||
BackgroundSprite = OffSprite;
|
||||
if (MouseOver) BackgroundSprite = HoverOffSprite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public CUITickBox() : base()
|
||||
{
|
||||
Absolute = new CUINullRect(w: 20, h: 20);
|
||||
BackgroundColor = Color.Cyan;
|
||||
Border.Color = Color.Transparent;
|
||||
ConsumeMouseClicks = true;
|
||||
ConsumeDragAndDrop = true;
|
||||
ConsumeSwipe = true;
|
||||
|
||||
|
||||
OffSprite = new CUISprite(CUI.CUITexturePath)
|
||||
{
|
||||
SourceRect = new Rectangle(0, 0, 32, 32),
|
||||
};
|
||||
|
||||
OnSprite = new CUISprite(CUI.CUITexturePath)
|
||||
{
|
||||
SourceRect = new Rectangle(32, 0, 32, 32),
|
||||
};
|
||||
|
||||
HoverOffSprite = new CUISprite(CUI.CUITexturePath)
|
||||
{
|
||||
SourceRect = new Rectangle(64, 0, 32, 32),
|
||||
};
|
||||
HoverOnSprite = new CUISprite(CUI.CUITexturePath)
|
||||
{
|
||||
SourceRect = new Rectangle(96, 0, 32, 32),
|
||||
};
|
||||
|
||||
DisabledSprite = new CUISprite(CUI.CUITexturePath)
|
||||
{
|
||||
SourceRect = new Rectangle(128, 0, 32, 32),
|
||||
};
|
||||
|
||||
ChangeSprite();
|
||||
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
if (Disabled) return;
|
||||
|
||||
SoundPlayer.PlayUISound(ClickSound);
|
||||
State = !State;
|
||||
OnStateChange?.Invoke(State);
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State));
|
||||
};
|
||||
|
||||
OnMouseEnter += (e) => ChangeSprite();
|
||||
OnMouseLeave += (e) => ChangeSprite();
|
||||
|
||||
OnConsume += (o) =>
|
||||
{
|
||||
if (o is bool b) State = b;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Just an example of what CUICanvas can be used for
|
||||
/// </summary>
|
||||
public class CUIWater : CUICanvas
|
||||
{
|
||||
public float Omega = 1.999f;
|
||||
|
||||
public float[,] Pool1;
|
||||
public float[,] Pool2;
|
||||
public float[,] DensityMap;
|
||||
|
||||
public Color[] ColorPalette = new Color[]{
|
||||
new Color(0,0,0,0),
|
||||
new Color(0,0,64),
|
||||
new Color(32,0,64),
|
||||
new Color(255,0,255),
|
||||
new Color(0,255,255),
|
||||
};
|
||||
|
||||
|
||||
public override Point Size
|
||||
{
|
||||
get => base.Size;
|
||||
set
|
||||
{
|
||||
base.Size = value;
|
||||
Pool1 = new float[Texture.Width, Texture.Height];
|
||||
Pool2 = new float[Texture.Width, Texture.Height];
|
||||
DensityMap = new float[Texture.Width, Texture.Height];
|
||||
RandomizeDensityMap();
|
||||
}
|
||||
}
|
||||
|
||||
public float NextAmplitude(int x, int y)
|
||||
{
|
||||
float avg = (
|
||||
Pool1[x + 1, y] +
|
||||
Pool1[x, y + 1] +
|
||||
Pool1[x - 1, y] +
|
||||
Pool1[x, y - 1]
|
||||
) / 4.0f;
|
||||
|
||||
return avg * Omega + (1 - Omega) * Pool2[x, y];
|
||||
}
|
||||
|
||||
public void Step()
|
||||
{
|
||||
for (int x = 1; x < Size.X - 1; x++)
|
||||
{
|
||||
for (int y = 1; y < Size.Y - 1; y++)
|
||||
{
|
||||
Pool2[x, y] = NextAmplitude(x, y) * DensityMap[x, y];
|
||||
}
|
||||
}
|
||||
|
||||
(Pool1, Pool2) = (Pool2, Pool1);
|
||||
}
|
||||
|
||||
public double UpdateInterval = 1.0 / 60.0;
|
||||
private double lastUpdateTime = -1;
|
||||
public void Update(double totalTime)
|
||||
{
|
||||
if (totalTime - lastUpdateTime < UpdateInterval) return;
|
||||
UpdateSelf();
|
||||
Step();
|
||||
lastUpdateTime = totalTime;
|
||||
|
||||
TransferData();
|
||||
}
|
||||
|
||||
public virtual void UpdateSelf()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void TransferData()
|
||||
{
|
||||
for (int x = 0; x < Size.X; x++)
|
||||
{
|
||||
for (int y = 0; y < Size.Y; y++)
|
||||
{
|
||||
SetPixel(x, y, ToolBox.GradientLerp(Math.Abs(Pool1[x, y]), ColorPalette));
|
||||
}
|
||||
}
|
||||
|
||||
SetData();
|
||||
}
|
||||
|
||||
public void RandomizeDensityMap()
|
||||
{
|
||||
for (int x = 0; x < Size.X; x++)
|
||||
{
|
||||
for (int y = 0; y < Size.Y; y++)
|
||||
{
|
||||
DensityMap[x, y] = 1.0f - CUI.Random.NextSingle() * 0.01f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float DropSize = 16.0f;
|
||||
public void Drop(float x, float y)
|
||||
{
|
||||
int i = (int)Math.Clamp(Math.Round(x * Texture.Width), 1, Texture.Width - 2);
|
||||
int j = (int)Math.Clamp(Math.Round(y * Texture.Height), 1, Texture.Height - 2);
|
||||
|
||||
Pool1[i, j] = DropSize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CUIWater(int x, int y) : base(x, y)
|
||||
{
|
||||
//ConsumeDragAndDrop = true;
|
||||
|
||||
//OnUpdate += Update;
|
||||
Pool1 = new float[Texture.Width, Texture.Height];
|
||||
Pool2 = new float[Texture.Width, Texture.Height];
|
||||
DensityMap = new float[Texture.Width, Texture.Height];
|
||||
RandomizeDensityMap();
|
||||
|
||||
// OnMouseOn += (e) =>
|
||||
// {
|
||||
// if (!MousePressed) return;
|
||||
// Vector2 v = CUIAnchor.AnchorFromPos(Real, e.MousePosition);
|
||||
// Drop(v.X, v.Y);
|
||||
// };
|
||||
}
|
||||
|
||||
public CUIWater() : this(256, 256)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user