Add quick interactions locally
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// WIP, can animate any property on any object
|
||||
/// Can run back and forth in [0..1] interval and
|
||||
/// interpolate any property between StartValue and EndValue
|
||||
/// </summary>
|
||||
public class CUIAnimation
|
||||
{
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnDispose += () => ActiveAnimations.Clear();
|
||||
}
|
||||
|
||||
public static HashSet<CUIAnimation> ActiveAnimations = new();
|
||||
/// <summary>
|
||||
/// This is called in CUIUpdate
|
||||
/// </summary>
|
||||
internal static void UpdateAllAnimations(double time)
|
||||
{
|
||||
foreach (CUIAnimation animation in ActiveAnimations)
|
||||
{
|
||||
animation.Step(time);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Debug { get; set; }
|
||||
public static float StartLambda = 0.0f;
|
||||
public static float EndLambda = 1.0f;
|
||||
|
||||
|
||||
private object target;
|
||||
/// <summary>
|
||||
/// Object containing animated property
|
||||
/// </summary>
|
||||
public object Target
|
||||
{
|
||||
get => target;
|
||||
set
|
||||
{
|
||||
target = value;
|
||||
UpdateSetter();
|
||||
}
|
||||
}
|
||||
private bool active;
|
||||
public bool Active
|
||||
{
|
||||
get => active;
|
||||
set
|
||||
{
|
||||
if (Blocked || active == value) return;
|
||||
active = value;
|
||||
|
||||
if (active) ActiveAnimations.Add(this);
|
||||
else ActiveAnimations.Remove(this);
|
||||
ApplyValue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In seconds
|
||||
/// </summary>
|
||||
public double Duration
|
||||
{
|
||||
get => 1.0 / Speed * Timing.Step;
|
||||
set
|
||||
{
|
||||
double steps = value / Timing.Step;
|
||||
Speed = 1.0 / steps;
|
||||
}
|
||||
}
|
||||
|
||||
public double ReverseDuration
|
||||
{
|
||||
get => 1.0 / (BackSpeed ?? 0) * Timing.Step;
|
||||
set
|
||||
{
|
||||
double steps = value / Timing.Step;
|
||||
BackSpeed = 1.0 / steps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will prevent it from starting
|
||||
/// </summary>
|
||||
public bool Blocked { get; set; }
|
||||
/// <summary>
|
||||
/// Progress of animation [0..1]
|
||||
/// </summary>
|
||||
public double Lambda { get; set; }
|
||||
/// <summary>
|
||||
/// Lambda increase per update step, calculated when you set Duration
|
||||
/// </summary>
|
||||
public double Speed { get; set; } = 0.01;
|
||||
public double? BackSpeed { get; set; }
|
||||
/// <summary>
|
||||
/// If true animation won't stop when reaching end, it will change direction
|
||||
/// </summary>
|
||||
public bool Bounce { get; set; }
|
||||
/// <summary>
|
||||
/// Straight, Reverse
|
||||
/// </summary>
|
||||
public CUIDirection Direction { get; set; }
|
||||
/// <summary>
|
||||
/// Value will be interpolated between these values
|
||||
/// </summary>
|
||||
public object StartValue { get; set; }
|
||||
public object EndValue { get; set; }
|
||||
|
||||
private string property;
|
||||
private Action<object> setter;
|
||||
private Type propertyType;
|
||||
/// <summary>
|
||||
/// Property name that is animated
|
||||
/// </summary>
|
||||
public string Property
|
||||
{
|
||||
get => property;
|
||||
set
|
||||
{
|
||||
property = value;
|
||||
UpdateSetter();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<CUIDirection> OnStop;
|
||||
/// <summary>
|
||||
/// You can set custon Interpolate function
|
||||
/// </summary>
|
||||
public Func<float, object> Interpolate
|
||||
{
|
||||
get => interpolate;
|
||||
set
|
||||
{
|
||||
customInterpolate = value;
|
||||
UpdateSetter();
|
||||
}
|
||||
}
|
||||
private Func<float, object> customInterpolate;
|
||||
private Func<float, object> interpolate;
|
||||
//...
|
||||
public Action<object> Convert<T>(Action<T> myActionT)
|
||||
{
|
||||
if (myActionT == null) return null;
|
||||
else return new Action<object>(o => myActionT((T)o));
|
||||
}
|
||||
|
||||
|
||||
private void UpdateSetter()
|
||||
{
|
||||
if (Target != null && Property != null)
|
||||
{
|
||||
PropertyInfo pi = Target.GetType().GetProperty(Property);
|
||||
if (pi == null)
|
||||
{
|
||||
CUI.Warning($"CUIAnimation couldn't find {Property} in {Target}");
|
||||
return;
|
||||
}
|
||||
|
||||
propertyType = pi.PropertyType;
|
||||
|
||||
interpolate = customInterpolate ?? ((l) => CUIInterpolate.Interpolate[propertyType].Invoke(StartValue, EndValue, l));
|
||||
|
||||
|
||||
// https://coub.com/view/1mast0
|
||||
if (propertyType == typeof(float))
|
||||
{
|
||||
setter = Convert<float>(pi.GetSetMethod()?.CreateDelegate<Action<float>>(Target));
|
||||
}
|
||||
|
||||
if (propertyType == typeof(Color))
|
||||
{
|
||||
setter = Convert<Color>(pi.GetSetMethod()?.CreateDelegate<Action<Color>>(Target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resumes animation in the same direction
|
||||
/// </summary>
|
||||
public void Start() => Active = true;
|
||||
public void Stop()
|
||||
{
|
||||
Active = false;
|
||||
OnStop?.Invoke(Direction);
|
||||
}
|
||||
/// <summary>
|
||||
/// Set Direction to Straight and Start
|
||||
/// </summary>
|
||||
public void Forward()
|
||||
{
|
||||
Direction = CUIDirection.Straight;
|
||||
Active = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Set Direction to Reverse and Start
|
||||
/// </summary>
|
||||
public void Back()
|
||||
{
|
||||
Direction = CUIDirection.Reverse;
|
||||
Active = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Lambda to 0
|
||||
/// </summary>
|
||||
public void SetToStart() => Lambda = StartLambda;
|
||||
/// <summary>
|
||||
/// Set Lambda to 1
|
||||
/// </summary>
|
||||
public void SetToEnd() => Lambda = EndLambda;
|
||||
|
||||
|
||||
private void UpdateState()
|
||||
{
|
||||
if (Direction == CUIDirection.Straight && Lambda >= EndLambda)
|
||||
{
|
||||
Lambda = EndLambda;
|
||||
if (Bounce) Direction = CUIDirection.Reverse;
|
||||
else Stop();
|
||||
}
|
||||
|
||||
if (Direction == CUIDirection.Reverse && Lambda <= StartLambda)
|
||||
{
|
||||
Lambda = StartLambda;
|
||||
if (Bounce) Direction = CUIDirection.Straight;
|
||||
else Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyValue()
|
||||
{
|
||||
if (interpolate == null) return;
|
||||
object value = interpolate.Invoke((float)Lambda);
|
||||
setter?.Invoke(value);
|
||||
}
|
||||
|
||||
public void Step(double time)
|
||||
{
|
||||
UpdateState();
|
||||
ApplyValue();
|
||||
Lambda += Direction == CUIDirection.Straight ? Speed : -(BackSpeed ?? Speed);
|
||||
if (Debug) LogStatus();
|
||||
}
|
||||
|
||||
public void LogStatus() => CUI.Log($"Active:{Active} Direction:{Direction} Lambda:{Lambda}");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing a few interpolate functions for CUIAnimation
|
||||
/// </summary>
|
||||
public class CUIInterpolate
|
||||
{
|
||||
public static object InterpolateColor(object start, object end, double lambda)
|
||||
{
|
||||
return ((Color)start).To(((Color)end), (float)lambda);
|
||||
}
|
||||
|
||||
public static object InterpolateVector2(object start, object end, double lambda)
|
||||
{
|
||||
Vector2 a = (Vector2)start;
|
||||
Vector2 b = (Vector2)end;
|
||||
return a + (b - a) * (float)lambda;
|
||||
}
|
||||
|
||||
public static object InterpolateFloat(object start, object end, double lambda)
|
||||
{
|
||||
float a = (float)start;
|
||||
float b = (float)end;
|
||||
return a + (b - a) * (float)lambda;
|
||||
}
|
||||
|
||||
public static Dictionary<Type, Func<object, object, double, object>> Interpolate = new();
|
||||
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () =>
|
||||
{
|
||||
Interpolate[typeof(Color)] = InterpolateColor;
|
||||
Interpolate[typeof(Vector2)] = InterpolateVector2;
|
||||
Interpolate[typeof(float)] = InterpolateFloat;
|
||||
};
|
||||
|
||||
CUI.OnDispose += () =>
|
||||
{
|
||||
Interpolate.Clear();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
301
Quick Interactions/CSharp/Client/CrabUI/CUI.cs
Normal file
301
Quick Interactions/CSharp/Client/CrabUI/CUI.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using HarmonyLib;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: IgnoresAccessChecksTo("Barotrauma")]
|
||||
[assembly: IgnoresAccessChecksTo("DedicatedServer")]
|
||||
[assembly: IgnoresAccessChecksTo("BarotraumaCore")]
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// In fact a static class managing static things
|
||||
/// </summary>
|
||||
public partial class CUI
|
||||
{
|
||||
public static Vector2 GameScreenSize => new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
public static Rectangle GameScreenRect => new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
|
||||
|
||||
private static string modDir;
|
||||
public static string ModDir
|
||||
{
|
||||
get => modDir;
|
||||
set
|
||||
{
|
||||
modDir = value;
|
||||
LuaFolder = Path.Combine(value, @"Lua");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UseLua { get; set; } = true;
|
||||
public static string LuaFolder { get; set; }
|
||||
|
||||
private static string assetsPath;
|
||||
public static string AssetsPath
|
||||
{
|
||||
get => assetsPath;
|
||||
set
|
||||
{
|
||||
assetsPath = value;
|
||||
PalettesPath = Path.Combine(value, @"Palettes");
|
||||
}
|
||||
}
|
||||
public static string CUITexturePath = "CUI.png";
|
||||
public static string PalettesPath { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If set CUI will also check this folder when loading textures
|
||||
/// </summary>
|
||||
public static string PGNAssets
|
||||
{
|
||||
get => TextureManager.PGNAssets;
|
||||
set => TextureManager.PGNAssets = value;
|
||||
}
|
||||
|
||||
private static List<CUI> Instances = new List<CUI>();
|
||||
/// <summary>
|
||||
/// The singleton
|
||||
/// </summary>
|
||||
public static CUI Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Instances.Count == 0) return null;
|
||||
return Instances.First();
|
||||
}
|
||||
set
|
||||
{
|
||||
Instances.Clear();
|
||||
if (value != null) Instances.Add(value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Orchestrates Drawing and updates, there could be only one
|
||||
/// CUI.Main is located under vanilla GUI
|
||||
/// </summary>
|
||||
public static CUIMainComponent Main => Instance?.main;
|
||||
/// <summary>
|
||||
/// Orchestrates Drawing and updates, there could be only one
|
||||
/// CUI.TopMain is located above vanilla GUI
|
||||
/// </summary>
|
||||
public static CUIMainComponent TopMain => Instance?.topMain;
|
||||
/// <summary>
|
||||
/// Snapshot of mouse and keyboard state
|
||||
/// </summary>
|
||||
public static CUIInput Input => Instance?.input;
|
||||
/// <summary>
|
||||
/// Safe texture manager
|
||||
/// </summary
|
||||
public static CUITextureManager TextureManager => Instance?.textureManager;
|
||||
/// <summary>
|
||||
/// Adapter to vanilla focus system, don't use
|
||||
/// </summary>
|
||||
public static CUIFocusResolver FocusResolver => Instance?.focusResolver;
|
||||
public static CUILuaRegistrar LuaRegistrar => Instance?.luaRegistrar;
|
||||
|
||||
public static CUIComponent FocusedComponent
|
||||
{
|
||||
get => FocusResolver.FocusedCUIComponent;
|
||||
set => FocusResolver.FocusedCUIComponent = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This affects logging
|
||||
/// </summary>
|
||||
public static bool Debug;
|
||||
/// <summary>
|
||||
/// Will break the mod if it's compiled
|
||||
/// </summary>
|
||||
public static bool UseCursedPatches { get; set; } = false;
|
||||
/// <summary>
|
||||
/// It's important to set it, if 2 CUIs try to add a hook with same id one won't be added
|
||||
/// </summary>
|
||||
public static string HookIdentifier
|
||||
{
|
||||
get => hookIdentifier;
|
||||
set
|
||||
{
|
||||
hookIdentifier = value?.Replace(' ', '_');
|
||||
}
|
||||
}
|
||||
private static string hookIdentifier = "";
|
||||
public static string CUIHookID => $"QICrabUI.{HookIdentifier}";
|
||||
public static Harmony harmony;
|
||||
public static Random Random = new Random();
|
||||
|
||||
/// <summary>
|
||||
/// Called on first Initialize
|
||||
/// </summary>
|
||||
public static event Action OnInit;
|
||||
/// <summary>
|
||||
/// Called on last Dispose
|
||||
/// </summary>
|
||||
public static event Action OnDispose;
|
||||
public static bool Disposed { get; set; } = true;
|
||||
public static event Action<TextInputEventArgs> OnWindowTextInput;
|
||||
public static event Action<TextInputEventArgs> OnWindowKeyDown;
|
||||
//public static event Action<TextInputEventArgs> OnWindowKeyUp;
|
||||
|
||||
//TODO this doesn't trigger when you press menu button, i need to go inside thet method
|
||||
public static event Action OnPauseMenuToggled;
|
||||
public static void InvokeOnPauseMenuToggled() => OnPauseMenuToggled?.Invoke();
|
||||
|
||||
public static bool InputBlockingMenuOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsBlockingPredicates == null) return false;
|
||||
return IsBlockingPredicates.Any(p => p());
|
||||
}
|
||||
}
|
||||
public static List<Func<bool>> IsBlockingPredicates => Instance?.isBlockingPredicates;
|
||||
private List<Func<bool>> isBlockingPredicates = new List<Func<bool>>();
|
||||
/// <summary>
|
||||
/// In theory multiple mods could use same CUI instance,
|
||||
/// i clean it up when UserCount drops to 0
|
||||
/// </summary>
|
||||
public static int UserCount = 0;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An object that contains current mouse and keyboard states
|
||||
/// It scans states at the start on Main.Update
|
||||
/// </summary>
|
||||
private CUIInput input = new CUIInput();
|
||||
private CUIMainComponent main;
|
||||
private CUIMainComponent topMain;
|
||||
private CUITextureManager textureManager = new CUITextureManager();
|
||||
private CUIFocusResolver focusResolver = new CUIFocusResolver();
|
||||
private CUILuaRegistrar luaRegistrar = new CUILuaRegistrar();
|
||||
|
||||
public static void ReEmitWindowTextInput(object sender, TextInputEventArgs e) => OnWindowTextInput?.Invoke(e);
|
||||
public static void ReEmitWindowKeyDown(object sender, TextInputEventArgs e) => OnWindowKeyDown?.Invoke(e);
|
||||
//public static void ReEmitWindowKeyUp(object sender, TextInputEventArgs e) => OnWindowKeyUp?.Invoke(e);
|
||||
|
||||
|
||||
private void CreateMains()
|
||||
{
|
||||
main = new CUIMainComponent() { AKA = "Main Component" };
|
||||
topMain = new CUIMainComponent() { AKA = "Top Main Component" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should be called in IAssemblyPlugin.Initialize
|
||||
/// \todo make it CUI instance member when plugin system settle
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
CUIDebug.Log($"CUI.Initialize {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
|
||||
if (Instance == null)
|
||||
{
|
||||
Disposed = false;
|
||||
Instance = new CUI();
|
||||
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
if (HookIdentifier == null || HookIdentifier == "") CUI.Warning($"Warning: CUI.HookIdentifier is not set, this mod may conflict with other mods that use CUI");
|
||||
|
||||
InitStatic();
|
||||
// this should init only static stuff that doesn't depend on instance
|
||||
OnInit?.Invoke();
|
||||
|
||||
Instance.CreateMains();
|
||||
|
||||
GameMain.Instance.Window.TextInput += ReEmitWindowTextInput;
|
||||
GameMain.Instance.Window.KeyDown += ReEmitWindowKeyDown;
|
||||
//GameMain.Instance.Window.KeyUp += ReEmitWindowKeyUp;
|
||||
CUIDebug.Log($"CUI.OnInit?.Invoke took {sw.ElapsedMilliseconds}ms");
|
||||
|
||||
sw.Restart();
|
||||
|
||||
harmony = new Harmony(CUIHookID);
|
||||
PatchAll();
|
||||
CUIDebug.Log($"CUI.PatchAll took {sw.ElapsedMilliseconds}ms");
|
||||
|
||||
AddCommands();
|
||||
|
||||
sw.Restart();
|
||||
LuaRegistrar.Register();
|
||||
CUIDebug.Log($"CUI.LuaRegistrar.Register took {sw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
UserCount++;
|
||||
|
||||
CUIDebug.Log($"CUI.Initialized {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
|
||||
}
|
||||
|
||||
public static void OnLoadCompleted()
|
||||
{
|
||||
//Idk doesn't work
|
||||
//CUIMultiModResolver.FindOtherInputs();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should be called in IAssemblyPlugin.Dispose
|
||||
/// </summary>
|
||||
public static void Dispose()
|
||||
{
|
||||
CUIDebug.Log($"CUI.Dispose {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
|
||||
|
||||
UserCount--;
|
||||
|
||||
if (UserCount <= 0)
|
||||
{
|
||||
RemoveCommands();
|
||||
// harmony.UnpatchAll(harmony.Id);
|
||||
harmony.UnpatchAll();
|
||||
TextureManager.Dispose();
|
||||
CUIDebugEventComponent.CapturedIDs.Clear();
|
||||
OnDispose?.Invoke();
|
||||
Disposed = true;
|
||||
|
||||
Instance.isBlockingPredicates.Clear();
|
||||
Errors.Clear();
|
||||
|
||||
LuaRegistrar.Deregister();
|
||||
|
||||
Instance = null;
|
||||
UserCount = 0;
|
||||
|
||||
CUIDebug.Log($"CUI.Disposed {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
|
||||
}
|
||||
|
||||
GameMain.Instance.Window.TextInput -= ReEmitWindowTextInput;
|
||||
GameMain.Instance.Window.KeyDown -= ReEmitWindowKeyDown;
|
||||
//GameMain.Instance.Window.KeyUp -= ReEmitWindowKeyUp;
|
||||
}
|
||||
|
||||
//HACK Why it's set to run in static constructor?
|
||||
// it runs perfectly fine in CUI.Initialize
|
||||
//TODO component inits doesn't depend on the order
|
||||
// why am i responsible for initing them here?
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUIExtensions.InitStatic();
|
||||
CUIInterpolate.InitStatic();
|
||||
CUIAnimation.InitStatic();
|
||||
CUIReflection.InitStatic();
|
||||
CUIMultiModResolver.InitStatic();
|
||||
CUIPalette.InitStatic();
|
||||
CUIMap.CUIMapLink.InitStatic();
|
||||
CUIMenu.InitStatic();
|
||||
CUIComponent.InitStatic();
|
||||
CUITypeMetaData.InitStatic();
|
||||
CUIStyleLoader.InitStatic();
|
||||
}
|
||||
}
|
||||
}
|
||||
208
Quick Interactions/CSharp/Client/CrabUI/Changelog.md
Normal file
208
Quick Interactions/CSharp/Client/CrabUI/Changelog.md
Normal file
@@ -0,0 +1,208 @@
|
||||
## 0.2.5.1
|
||||
Experimenting with the way multiple CUIs resolve conflicts
|
||||
Renamed CUI.UpdateHookIdentifier => CUI.HookIdentifier
|
||||
now i'm using it in harmony patches to
|
||||
|
||||
added warning if it's not set
|
||||
|
||||
fixed crash in GUI_UpdateMouseOn_Postfix
|
||||
Added null checks in GUI_UpdateMouseOn_Postfix
|
||||
|
||||
## 0.2.5.0
|
||||
Added CUI.UpdateHookIdentifier it will be set as identifier to CUI think hook, it very important to set it or hooks from different CUIs will conflict
|
||||
|
||||
Added CUIAnimation
|
||||
Added IgnoreTransparent prop, if true mouse events will work only on not transparent sprites
|
||||
Added Transparency prop, it multiplies BackgroundColor.A and is propagated to Children
|
||||
|
||||
Made CUISpite an object... again
|
||||
Added Rotation, Origin and Offset to CUISprite
|
||||
Added option to load CUISprites with base folder, which allows deserialized components to load sprites from the same folder with relative paths
|
||||
|
||||
Added CUIMenu, check "Custom Menus" mod, CUIRadialMenu (the ugly brother of CUIMenu)
|
||||
|
||||
Added more docs
|
||||
|
||||
## 0.2.4.0
|
||||
|
||||
"Fixed" cursed bug that made MainComponents become in GameMain.Update patch after multiple lobbies in compiled version
|
||||
But this "fix" seems to decrease update smoothness, so i might rethink later
|
||||
Set CUI.UseCursedPatches to true if you're not affraid
|
||||
|
||||
Added more performance measurements, shortcutted dumb class scanning in CUILuaRegistrar that happened even if you didn't use lua
|
||||
|
||||
Buttons now update their color only on events and not in draw cycle, added AutoUpdateColor to prevent this color change in case you want to control it manually (why?)
|
||||
|
||||
Added confusing event InvokeOnMouseOff which is symmetrical to InvokeOnMouseOn but happens on previous MouseOn list, and it turned out to be essential to e.g. switch color when mouse leaves a button
|
||||
|
||||
You can now limit resize directions with CUIComponent.ResizeDirection
|
||||
|
||||
Fixed forsed size not reseting after removing a textblock
|
||||
|
||||
Added cuiprinttree command along with cuidraworder
|
||||
|
||||
|
||||
## 0.2.3.0
|
||||
|
||||
Made CUITextInput, CUITickBox and CUISlider use commands and consume data
|
||||
|
||||
Added Gap to CUIVerticalList
|
||||
|
||||
Made OnAnyCommand,OnAnyData,OnConsume events instead of delegates
|
||||
|
||||
added ReflectCommands and RetranslateCommands props, so you could define in xml that a wrapper should sync state between its children
|
||||
|
||||
Setting a Palette prop now won't automatically set palette for all children because it doesn't work as intended on deserialized components, use DeepPalette instead
|
||||
|
||||
CUISlider now have Range and Precision
|
||||
|
||||
CUI.OnPauseMenuToggled will now trigger even when resume button in pause menu is pressed
|
||||
|
||||
You can no just set CUIPalette.DefaultPalette before CUI.Initialize instead of manually loading it
|
||||
|
||||
Palettes now remember their BaseColor so you could replicate them
|
||||
|
||||
Added more useless CUIPrefabs, i think they are too specific and need some redesign, perhaps i need to create some builder
|
||||
|
||||
Added FloatRange alongside with IntRange
|
||||
|
||||
fixed crash in KeyboardDispatcher_set_Subscriber_Replace in compiled mods
|
||||
|
||||
## 0.2.2.1
|
||||
|
||||
Minor stuff: multibutton dispatches the command, CUIPages resizes page to [0,0,1,1], maincomponent flatten tree is a bit more thread safe
|
||||
|
||||
Added IRefreshable interface and CUIComponent.CascadeRefresh
|
||||
CascadeRefresh will recursivelly call Refresh on every child that implements IRefreshable
|
||||
|
||||
## 0.2.2.0
|
||||
|
||||
Added to CUI.cs
|
||||
```
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: IgnoresAccessChecksTo("Barotrauma")]
|
||||
[assembly: IgnoresAccessChecksTo("DedicatedServer")]
|
||||
[assembly: IgnoresAccessChecksTo("BarotraumaCore")]
|
||||
```
|
||||
So it could be compiled
|
||||
|
||||
#### Temporary solution to pathing:
|
||||
|
||||
Now mod won't automatially find its folders
|
||||
|
||||
If you want to use lua you need to set CUI.ModDir to the mod folder path
|
||||
|
||||
Also you need to place Assets folder with CUI stuff somewhere in your mod and set CUI.AssetsPath to it
|
||||
You can rename it, just set the path
|
||||
|
||||
All this needs to be done before CUI.Initialize()
|
||||
|
||||
## 0.2.1.0
|
||||
|
||||
Dried tree building methods, added tests for them
|
||||
|
||||
Added insert method along with append and prepend, unlike List.Insert it won't throw on "index out of bounds"
|
||||
|
||||
## 0.2.0.1
|
||||
|
||||
.nojekyll moment
|
||||
|
||||
## 0.2.0.0
|
||||
|
||||
Reworked CUIPalette, and CUICommand, check docs
|
||||
|
||||
Reworked border, added separate borders for each side, border sprite, outline
|
||||
|
||||
Changed how zindex is calculated, now every next child will have higher zindex -> everything in one frame will be above or below everything in the other
|
||||
|
||||
optimized CUITextBlock measurements, added some validation to CUITextInput
|
||||
|
||||
Added CUIPresets with... presets. Which you can use to reduce boilerplate code
|
||||
|
||||
Made more stuff parsable and serializble
|
||||
|
||||
And tons of other things i'm too lazy to write down, compare commits if you're curious
|
||||
|
||||
|
||||
## 0.1.0.0
|
||||
|
||||
You can now use relative paths for sprite textures
|
||||
You can set CUI.PGNAssets to the folder with your assets, CUI will also search for textures there
|
||||
|
||||
Reworked MasterColorOpaque, it now just uses base color alpha
|
||||
|
||||
Synced vertical and horizontal lists, added ScrollSpeed
|
||||
|
||||
OnUpdate event now invoked before layout calcs, Also added OnLayoutUpdated event, it's invoked before recalc of children layouts
|
||||
|
||||
"Fixed" imprecise rects that e.g. caused sprite overlap and gaps
|
||||
|
||||
Added CrossRelative prop, it's like Relative but values are relative to the oposite value, e.g. CrossRelative.Width = Host.Real.Height, convinient for making square things
|
||||
|
||||
Reworked slider component
|
||||
|
||||
DragHandle can now DragRelative to the parent
|
||||
|
||||
#### Serialization
|
||||
|
||||
Added BreakSerialization prop, if true serrialization will stop on this component
|
||||
|
||||
Added LoadSelfFromFile, SaveToTheSamePath methods
|
||||
|
||||
Added Hydrate method, it will run right after deserizlization, allowing you to access children with Get<> and e.g. save them to local vars
|
||||
|
||||
Added SaveAfterLoad prop, it's mostly for serialization debugging
|
||||
|
||||
Added more Custom ToString and parsed methods to CUIExtensions, Added native enum serialization, Vector2 and Rectangle is now serialized into [ x, y ], be carefull
|
||||
|
||||
Sprite is now fully serializable
|
||||
|
||||
## 0.0.5.1
|
||||
|
||||
Added "[CUISerializable] public string Command { get; set; }"" to CUIButton so you could define command that is called on click in xml
|
||||
|
||||
Splitted MasterColor into MasterColor and MasterColorOpaque
|
||||
|
||||
CUITextBlock RealTextSize is now rounded because 2.199999 is prone to floating-point errors
|
||||
|
||||
Added MasterColor to CUIToggleButton
|
||||
|
||||
Buttons now will folow a pattern: if state is changed by user input then it'll invoke StateChanged event
|
||||
If state is changed from code then it won't invoke the event
|
||||
|
||||
When you change state from code you already know about that so you don't need an event
|
||||
And on the other side if event is always fired it will create un-untangleable loops when you try to sync state between two buttons
|
||||
|
||||
Fixed CUIComponent.Get< T > method, i forgot to add it to docs, it gives you memorized component by its name, so it's same as frame["header"], but it can also give you nested components like that `Header = Frame.Get<CUIHorizontalList>("layout.content.header");`
|
||||
|
||||
Exposed ResizeHandles, you can hide them separately
|
||||
|
||||
Fixed crash when serializing a Vector2, added more try-catches and warnings there
|
||||
|
||||
Fixed Sprites in CUI.png being 33x33, i just created a wrong first rectangle and then copy-pasted it, guh
|
||||
|
||||
Added sprites to resize handles, and gradient sprite that's not used yet
|
||||
|
||||
Added `public SpriteEffects Effects;` to CUISprite, it controls texture flipping
|
||||
|
||||
More params in CUITextureManager.GetSprite
|
||||
|
||||
## 0.0.5.0
|
||||
|
||||
Added Styles
|
||||
|
||||
Every component has a Style and every Type has a DefaultStyle
|
||||
|
||||
Styles are observable dicts with pairs { "prop name", "prop value" } and can be used to assign any parsable string to any prop or reference some value from CUIPalette
|
||||
|
||||
## 0.0.4.0
|
||||
Added CUICanvas.Render(Action< SpriteBatch > renderFunc) method that allows you to render anything you want onto the canvas texture
|
||||
|
||||
## 0.0.3.0
|
||||
Added Changelog.md :BaroDev:
|
||||
|
||||
Added CUI.TopMain, it's the second CUIMainComponent which exists above vanilla GUI
|
||||
|
||||
Added printsprites command, it prints styles from GUIStyle.ComponentStyles
|
||||
|
||||
More fabric methods for CUISprite
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebug.cs
Normal file
95
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebug.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
#define CUIDEBUG
|
||||
// #define SHOWPERF
|
||||
|
||||
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;
|
||||
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public static class CUIDebug
|
||||
{
|
||||
public static bool PrintKeys;
|
||||
|
||||
#if !CUIDEBUG
|
||||
[Conditional("DONT")]
|
||||
#endif
|
||||
public static void Log(object msg, Color? cl = null)
|
||||
{
|
||||
if (!CUI.Debug) return;
|
||||
cl ??= Color.Yellow;
|
||||
LuaCsLogger.LogMessage($"{msg ?? "null"}", cl * 0.8f, cl);
|
||||
}
|
||||
|
||||
|
||||
#if !CUIDEBUG
|
||||
[Conditional("DONT")]
|
||||
#endif
|
||||
public static void Info(object msg, Color? cl = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
if (!CUI.Debug) return;
|
||||
cl ??= Color.Cyan;
|
||||
var fi = new FileInfo(source);
|
||||
|
||||
CUI.Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", cl * 0.5f);
|
||||
CUI.Log(msg, cl);
|
||||
}
|
||||
|
||||
#if !CUIDEBUG
|
||||
[Conditional("DONT")]
|
||||
#endif
|
||||
public static void Error(object msg, Color? cl = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
if (!CUI.Debug) return;
|
||||
cl ??= Color.Orange;
|
||||
var fi = new FileInfo(source);
|
||||
|
||||
CUI.Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", cl * 0.5f);
|
||||
CUI.Log(msg, cl);
|
||||
}
|
||||
|
||||
#if !CUIDEBUG
|
||||
[Conditional("DONT")]
|
||||
#endif
|
||||
public static void Capture(CUIComponent host, CUIComponent target, string method, string sprop, string tprop, string value)
|
||||
{
|
||||
if (target == null || target.IgnoreDebug || !target.Debug) return;
|
||||
|
||||
//CUI.Log($"{host} {target} {method} {sprop} {tprop} {value}");
|
||||
|
||||
CUIDebugWindow.Main?.Capture(new CUIDebugEvent(host, target, method, sprop, tprop, value));
|
||||
}
|
||||
|
||||
#if !CUIDEBUG
|
||||
[Conditional("DONT")]
|
||||
#endif
|
||||
public static void Flush() => CUIDebugWindow.Main?.Flush();
|
||||
|
||||
|
||||
// public static int CUIShowperfCategory = 1000;
|
||||
// #if (!SHOWPERF || !CUIDEBUG)
|
||||
// [Conditional("DONT")]
|
||||
// #endif
|
||||
// public static void CaptureTicks(double ticks, string name, int hash) => ShowPerfExtensions.Plugin.CaptureTicks(ticks, CUIShowperfCategory, name, hash);
|
||||
|
||||
// #if (!SHOWPERF || !CUIDEBUG)
|
||||
// [Conditional("DONT")]
|
||||
// #endif
|
||||
// public static void CaptureTicks(double ticks, string name) => ShowPerfExtensions.Plugin.CaptureTicks(ticks, CUIShowperfCategory, name);
|
||||
|
||||
// #if (!SHOWPERF || !CUIDEBUG)
|
||||
// [Conditional("DONT")]
|
||||
// #endif
|
||||
// public static void EnsureCategory() => ShowPerfExtensions.Plugin.EnsureCategory(CUIShowperfCategory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
#define CUIDEBUG
|
||||
|
||||
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;
|
||||
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class CUIDebugEvent
|
||||
{
|
||||
public CUIComponent Host;
|
||||
public CUIComponent Target;
|
||||
public string Method;
|
||||
public string SProp;
|
||||
public string TProp;
|
||||
public string Value;
|
||||
public CUIDebugEvent(CUIComponent host, CUIComponent target, string method, string sprop, string tprop, string value)
|
||||
{
|
||||
Host = host;
|
||||
Target = target;
|
||||
Method = method ?? "";
|
||||
SProp = sprop ?? "";
|
||||
TProp = tprop ?? "";
|
||||
Value = value ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CUIDebugEventComponent : CUIComponent
|
||||
{
|
||||
public static Dictionary<int, Color> CapturedIDs = new Dictionary<int, Color>();
|
||||
|
||||
|
||||
|
||||
private CUIDebugEvent _value; public CUIDebugEvent Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
|
||||
Revealed = value != null;
|
||||
if (value != null)
|
||||
{
|
||||
LastUpdate = Timing.TotalTime;
|
||||
AssignColor();
|
||||
}
|
||||
MakeText();
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush() => Value = null;
|
||||
|
||||
private void MakeText()
|
||||
{
|
||||
if (Value == null)
|
||||
{
|
||||
Line1 = "";
|
||||
Line2 = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
Line1 = $" {Value.Target} in {Value.Host}.{Value.Method}";
|
||||
Line2 = $" {Value.SProp} -> {Value.TProp} {Value.Value}";
|
||||
}
|
||||
}
|
||||
|
||||
public static Random random = new Random();
|
||||
|
||||
private static float NextColor;
|
||||
private static float ColorShift = 0.05f;
|
||||
private void AssignColor()
|
||||
{
|
||||
if (Value.Target == null) return;
|
||||
|
||||
if (CapturedIDs.ContainsKey(Value.Target.ID))
|
||||
{
|
||||
BackgroundColor = CapturedIDs[Value.Target.ID];
|
||||
}
|
||||
else
|
||||
{
|
||||
// float r = random.NextSingle();
|
||||
// float scale = 20;
|
||||
// r = (float)Math.Round(r * scale) / scale;
|
||||
|
||||
CapturedIDs[Value.Target.ID] = GetColor(NextColor);
|
||||
|
||||
NextColor += ColorShift;
|
||||
if (NextColor > 1) NextColor = 0;
|
||||
|
||||
BackgroundColor = CapturedIDs[Value.Target.ID];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string Line1 = "";
|
||||
public string Line2 = "";
|
||||
|
||||
public float UpdateTimer;
|
||||
public double LastUpdate;
|
||||
|
||||
public Color GetColor(float d)
|
||||
{
|
||||
return ToolBox.GradientLerp(d,
|
||||
Color.Cyan * 0.5f,
|
||||
Color.Red * 0.5f,
|
||||
Color.Green * 0.5f,
|
||||
Color.Blue * 0.5f,
|
||||
Color.Magenta * 0.5f,
|
||||
Color.Yellow * 0.5f,
|
||||
Color.Cyan * 0.5f
|
||||
);
|
||||
}
|
||||
public Color GetColor2(float d)
|
||||
{
|
||||
return ToolBox.GradientLerp(Math.Min(0.8f, d),
|
||||
CapturedIDs[Value.Target.ID],
|
||||
Color.Black * 0.5f
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
BackgroundColor = GetColor2((float)(Timing.TotalTime - LastUpdate));
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
GUIStyle.Font.Value.DrawString(spriteBatch, Line1, Real.Position, Color.White, rotation: 0, origin: Vector2.Zero, 0.9f, se: SpriteEffects.None, layerDepth: 0.1f);
|
||||
|
||||
GUIStyle.Font.Value.DrawString(spriteBatch, Line2, Real.Position + new Vector2(0, 20), Color.White, rotation: 0, origin: Vector2.Zero, 0.9f, se: SpriteEffects.None, layerDepth: 0.1f);
|
||||
}
|
||||
|
||||
|
||||
public CUIDebugEventComponent(CUIDebugEvent value = null) : base()
|
||||
{
|
||||
Value = value;
|
||||
IgnoreDebug = true;
|
||||
BackgroundColor = Color.Green;
|
||||
Absolute = new CUINullRect(null, null, null, 40);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
223
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebugWindow.cs
Normal file
223
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebugWindow.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
#define CUIDEBUG
|
||||
|
||||
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;
|
||||
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class CUIDebugWindow : CUIFrame
|
||||
{
|
||||
public static CUIDebugWindow Main;
|
||||
|
||||
public CUIVerticalList EventsComponent;
|
||||
public CUIVerticalList DebugIDsComponent;
|
||||
public CUIPages Pages;
|
||||
public CUIMultiButton PickIDButton;
|
||||
|
||||
public List<CUIDebugEventComponent> Events = new List<CUIDebugEventComponent>();
|
||||
public int target;
|
||||
public bool Loop { get; set; } = true;
|
||||
|
||||
|
||||
|
||||
public void Capture(CUIDebugEvent e)
|
||||
{
|
||||
if (EventsComponent == null) return;
|
||||
|
||||
if (target > 200) return;
|
||||
|
||||
if (Events.Count < target + 1)
|
||||
{
|
||||
CUIDebugEventComponent ec = new CUIDebugEventComponent(e);
|
||||
Events.Add(ec);
|
||||
EventsComponent.Append(ec);
|
||||
|
||||
ec.OnMouseEnter += (m) => ec.Value.Target.DebugHighlight = true;
|
||||
ec.OnMouseLeave += (m) => ec.Value.Target.DebugHighlight = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Events[target].Value = e;
|
||||
}
|
||||
|
||||
target++;
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if (Loop) target = 0;
|
||||
//Events.ForEach(e => e.Flush());
|
||||
}
|
||||
|
||||
public void MakeIDList()
|
||||
{
|
||||
DebugIDsComponent.Visible = false;
|
||||
DebugIDsComponent.RemoveAllChildren();
|
||||
|
||||
List<CUIComponent> l = new List<CUIComponent>();
|
||||
|
||||
if (CUI.Main is not null)
|
||||
{
|
||||
RunRecursiveOn(CUI.Main, (component) =>
|
||||
{
|
||||
if (!component.IgnoreDebug) l.Add(component);
|
||||
});
|
||||
}
|
||||
|
||||
foreach (CUIComponent c in l)
|
||||
{
|
||||
CUIToggleButton b = new CUIToggleButton(c.ToString())
|
||||
{
|
||||
State = c.Debug,
|
||||
IgnoreDebug = true,
|
||||
Style = new CUIStyle(){
|
||||
{"TextAlign", "[0,0]"}
|
||||
},
|
||||
AddOnMouseDown = (m) =>
|
||||
{
|
||||
c.Debug = !c.Debug;
|
||||
MakeIDList();
|
||||
},
|
||||
AddOnMouseEnter = (m) => c.DebugHighlight = true,
|
||||
AddOnMouseLeave = (m) => c.DebugHighlight = false,
|
||||
};
|
||||
|
||||
DebugIDsComponent.Append(b);
|
||||
}
|
||||
DebugIDsComponent.Visible = true;
|
||||
l.Clear();
|
||||
}
|
||||
|
||||
public CUIDebugWindow() : base()
|
||||
{
|
||||
this.ZIndex = 1000;
|
||||
this.Layout = new CUILayoutVerticalList();
|
||||
|
||||
this["handle"] = new CUIComponent()
|
||||
{
|
||||
Absolute = new CUINullRect(null, null, null, 20),
|
||||
};
|
||||
|
||||
this["handle"]["closebutton"] = new CUIButton("X")
|
||||
{
|
||||
Anchor = new Vector2(1, 0.5f),
|
||||
Style = new CUIStyle(){
|
||||
{"MasterColor", "Red"}
|
||||
},
|
||||
AddOnMouseDown = (e) =>
|
||||
{
|
||||
CUIDebugWindow.Close();
|
||||
},
|
||||
};
|
||||
|
||||
this["controls"] = new CUIComponent()
|
||||
{
|
||||
FitContent = new CUIBool2(false, true),
|
||||
};
|
||||
|
||||
this["controls"]["loop"] = new CUIToggleButton("loop")
|
||||
{
|
||||
Relative = new CUINullRect(0, 0, 0.5f, null),
|
||||
AddOnStateChange = (state) =>
|
||||
{
|
||||
Loop = state;
|
||||
Events?.Clear();
|
||||
EventsComponent?.RemoveAllChildren();
|
||||
},
|
||||
State = Loop,
|
||||
};
|
||||
|
||||
|
||||
this["controls"].Append(PickIDButton = new CUIMultiButton()
|
||||
{
|
||||
Relative = new CUINullRect(0.5f, 0, 0.5f, null),
|
||||
Style = new CUIStyle(){
|
||||
{"InactiveColor", "0,0,0,128"},
|
||||
{"MousePressedColor", "0,255,255,64"}
|
||||
},
|
||||
ConsumeDragAndDrop = false,
|
||||
|
||||
Options = new string[]{
|
||||
"Debug events", "Debugged components"
|
||||
}
|
||||
});
|
||||
|
||||
Append(Pages = new CUIPages()
|
||||
{
|
||||
FillEmptySpace = new CUIBool2(false, true),
|
||||
Style = new CUIStyle(){
|
||||
{"BackgroundColor", "0,0,32,128"}
|
||||
},
|
||||
IgnoreDebug = true,
|
||||
});
|
||||
|
||||
EventsComponent = new CUIVerticalList()
|
||||
{
|
||||
Relative = new CUINullRect(0, 0, 1, 1),
|
||||
Scrollable = true,
|
||||
IgnoreDebug = true,
|
||||
};
|
||||
|
||||
DebugIDsComponent = new CUIVerticalList()
|
||||
{
|
||||
Relative = new CUINullRect(0, 0, 1, 1),
|
||||
Scrollable = true,
|
||||
IgnoreDebug = true,
|
||||
};
|
||||
|
||||
PickIDButton.OnSelect += (s) =>
|
||||
{
|
||||
if (PickIDButton.SelectedIndex == 0)
|
||||
{
|
||||
MakeIDList();
|
||||
Pages.Open(EventsComponent);
|
||||
}
|
||||
else Pages.Open(DebugIDsComponent);
|
||||
};
|
||||
PickIDButton.Select(0);
|
||||
|
||||
this["controls"].Get<CUIToggleButton>("loop").State = true;
|
||||
|
||||
IgnoreDebug = true;
|
||||
}
|
||||
|
||||
public static CUIDebugWindow Open()
|
||||
{
|
||||
if (CUI.Main == null) return null;
|
||||
|
||||
CUIDebugWindow w = new CUIDebugWindow()
|
||||
{
|
||||
Absolute = new CUINullRect(10, 370, 500, 370),
|
||||
};
|
||||
CUI.Main.Append(w);
|
||||
CUIDebugWindow.Main = w;
|
||||
CUI.Main.OnTreeChanged += () => w.MakeIDList();
|
||||
return w;
|
||||
}
|
||||
|
||||
public static void Close()
|
||||
{
|
||||
if (CUIDebugWindow.Main == null) return;
|
||||
|
||||
CUIDebugWindow.Main.RemoveSelf();
|
||||
CUIDebugWindow.Main.Revealed = false;
|
||||
CUIDebugWindow.Main = null;
|
||||
}
|
||||
|
||||
public CUIDebugWindow(float? x = null, float? y = null, float? w = null, float? h = null) : this()
|
||||
{
|
||||
Relative = new CUINullRect(x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
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
|
||||
{
|
||||
public class CUIDragHandle : ICUIVitalizable
|
||||
{
|
||||
public void SetHost(CUIComponent host) => Host = host;
|
||||
public CUIComponent Host;
|
||||
public Vector2 GrabOffset;
|
||||
public bool Grabbed;
|
||||
public bool Draggable;
|
||||
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||
/// <summary>
|
||||
/// If true, will change relative prop instead of Absolute
|
||||
/// </summary>
|
||||
public bool DragRelative { get; set; } = false;
|
||||
public bool OutputRealPos { get; set; } = false;
|
||||
|
||||
public bool ShouldStart(CUIInput input)
|
||||
{
|
||||
return Draggable && (
|
||||
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||
);
|
||||
}
|
||||
public void BeginDrag(Vector2 cursorPos)
|
||||
{
|
||||
Grabbed = true;
|
||||
GrabOffset = cursorPos - CUIAnchor.PosIn(Host);
|
||||
}
|
||||
|
||||
public void EndDrag()
|
||||
{
|
||||
Grabbed = false;
|
||||
Host.MainComponent?.OnDragEnd(this);
|
||||
}
|
||||
|
||||
//TODO test in 3d child offset
|
||||
public void DragTo(Vector2 to)
|
||||
{
|
||||
Vector2 pos = Host.Parent.ChildrenOffset.ToPlaneCoords(
|
||||
to - GrabOffset - CUIAnchor.PosIn(Host.Parent.Real, Host.ParentAnchor ?? Host.Anchor)
|
||||
);
|
||||
|
||||
if (DragRelative)
|
||||
{
|
||||
Vector2 newRelPos = new Vector2(
|
||||
pos.X / Host.Parent.Real.Width,
|
||||
pos.Y / Host.Parent.Real.Height
|
||||
);
|
||||
Host.CUIProps.Relative.SetValue(Host.Relative with { Position = newRelPos });
|
||||
Host.InvokeOnDrag(newRelPos.X, newRelPos.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host.CUIProps.Absolute.SetValue(Host.Absolute with { Position = pos });
|
||||
if (OutputRealPos) Host.InvokeOnDrag(to.X, to.Y);
|
||||
else Host.InvokeOnDrag(pos.X, pos.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public CUIDragHandle() { }
|
||||
public CUIDragHandle(CUIComponent host) => Host = host;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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
|
||||
{
|
||||
public class CUIFocusHandle : ICUIVitalizable
|
||||
{
|
||||
public void SetHost(CUIComponent host) => Host = host;
|
||||
public CUIComponent Host;
|
||||
public bool Focusable;
|
||||
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||
|
||||
public bool ShouldStart(CUIInput input)
|
||||
{
|
||||
return Focusable && (
|
||||
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||
);
|
||||
}
|
||||
|
||||
public CUIFocusHandle() { }
|
||||
public CUIFocusHandle(CUIComponent host) => Host = host;
|
||||
}
|
||||
}
|
||||
137
Quick Interactions/CSharp/Client/CrabUI/Events/CUIInput.cs
Normal file
137
Quick Interactions/CSharp/Client/CrabUI/Events/CUIInput.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
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>
|
||||
/// Containing a snapshot of current mouse and keyboard state
|
||||
/// </summary>
|
||||
public class CUIInput
|
||||
{
|
||||
public static double DoubleClickInterval = 0.2;
|
||||
public static float ScrollSpeed = 0.6f;
|
||||
|
||||
|
||||
|
||||
public MouseState Mouse;
|
||||
public bool MouseDown;
|
||||
public bool DoubleClick;
|
||||
public bool MouseUp;
|
||||
public bool MouseHeld;
|
||||
public float Scroll;
|
||||
public bool Scrolled;
|
||||
public Vector2 MousePosition;
|
||||
public Vector2 MousePositionDif;
|
||||
public bool MouseMoved;
|
||||
//TODO split into sh mouse and sh keyboard
|
||||
public bool SomethingHappened;
|
||||
|
||||
//HACK rethink, this is too hacky
|
||||
public bool ClickConsumed;
|
||||
|
||||
public KeyboardState Keyboard;
|
||||
public Keys[] HeldKeys = new Keys[0];
|
||||
public Keys[] PressedKeys = new Keys[0];
|
||||
public Keys[] UnpressedKeys = new Keys[0];
|
||||
public bool SomeKeyHeld;
|
||||
public bool SomeKeyPressed;
|
||||
public bool SomeKeyUnpressed;
|
||||
public TextInputEventArgs[] WindowTextInputEvents;
|
||||
public TextInputEventArgs[] WindowKeyDownEvents;
|
||||
public bool SomeWindowEvents;
|
||||
|
||||
|
||||
//-------------- private stuff
|
||||
private double PrevMouseDownTiming;
|
||||
private int PrevScrollWheelValue;
|
||||
private MouseState PrevMouseState;
|
||||
private Vector2 PrevMousePosition;
|
||||
private Keys[] PrevHeldKeys = new Keys[0];
|
||||
private Queue<TextInputEventArgs> WindowTextInputQueue = new Queue<TextInputEventArgs>(10);
|
||||
private Queue<TextInputEventArgs> WindowKeyDownQueue = new Queue<TextInputEventArgs>(10);
|
||||
|
||||
//HACK super hacky solution to block input from one CUIMainComponent to another
|
||||
public bool MouseInputHandled { get; set; }
|
||||
|
||||
public void Scan(double totalTime)
|
||||
{
|
||||
MouseInputHandled = false;
|
||||
ScanMouse(totalTime);
|
||||
ScanKeyboard(totalTime);
|
||||
}
|
||||
|
||||
private void ScanMouse(double totalTime)
|
||||
{
|
||||
ClickConsumed = false;
|
||||
|
||||
Mouse = Microsoft.Xna.Framework.Input.Mouse.GetState();
|
||||
|
||||
MouseDown = PrevMouseState.LeftButton == ButtonState.Released && Mouse.LeftButton == ButtonState.Pressed;
|
||||
MouseUp = PrevMouseState.LeftButton == ButtonState.Pressed && Mouse.LeftButton == ButtonState.Released;
|
||||
MouseHeld = Mouse.LeftButton == ButtonState.Pressed;
|
||||
|
||||
PrevMousePosition = MousePosition;
|
||||
MousePosition = new Vector2(Mouse.Position.X, Mouse.Position.Y);
|
||||
MousePositionDif = MousePosition - PrevMousePosition;
|
||||
MouseMoved = MousePositionDif != Vector2.Zero;
|
||||
|
||||
Scroll = (Mouse.ScrollWheelValue - PrevScrollWheelValue) * ScrollSpeed;
|
||||
PrevScrollWheelValue = Mouse.ScrollWheelValue;
|
||||
Scrolled = Scroll != 0;
|
||||
|
||||
DoubleClick = false;
|
||||
|
||||
if (MouseDown)
|
||||
{
|
||||
if (totalTime - PrevMouseDownTiming < DoubleClickInterval)
|
||||
{
|
||||
DoubleClick = true;
|
||||
}
|
||||
|
||||
PrevMouseDownTiming = totalTime;
|
||||
}
|
||||
|
||||
SomethingHappened = MouseHeld || MouseUp || MouseDown || MouseMoved || Scrolled;
|
||||
|
||||
PrevMouseState = Mouse;
|
||||
}
|
||||
|
||||
private void ScanKeyboard(double totalTime)
|
||||
{
|
||||
Keyboard = Microsoft.Xna.Framework.Input.Keyboard.GetState();
|
||||
HeldKeys = Keyboard.GetPressedKeys();
|
||||
SomeKeyHeld = HeldKeys.Length > 0;
|
||||
|
||||
PressedKeys = HeldKeys.Except(PrevHeldKeys).ToArray();
|
||||
UnpressedKeys = PrevHeldKeys.Except(HeldKeys).ToArray();
|
||||
|
||||
SomeKeyPressed = PressedKeys.Length > 0;
|
||||
SomeKeyUnpressed = UnpressedKeys.Length > 0;
|
||||
|
||||
PrevHeldKeys = HeldKeys;
|
||||
|
||||
WindowTextInputEvents = WindowTextInputQueue.ToArray();
|
||||
WindowTextInputQueue.Clear();
|
||||
|
||||
WindowKeyDownEvents = WindowKeyDownQueue.ToArray();
|
||||
WindowKeyDownQueue.Clear();
|
||||
|
||||
|
||||
SomeWindowEvents = WindowTextInputEvents.Length > 0 || WindowKeyDownEvents.Length > 0;
|
||||
}
|
||||
|
||||
public CUIInput()
|
||||
{
|
||||
CUI.OnWindowKeyDown += (e) => WindowKeyDownQueue.Enqueue(e);
|
||||
CUI.OnWindowTextInput += (e) => WindowTextInputQueue.Enqueue(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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
|
||||
{
|
||||
public class CUIResizeHandle : ICUIVitalizable
|
||||
{
|
||||
public void SetHost(CUIComponent host) => Host = host;
|
||||
public CUIComponent Host;
|
||||
public CUIRect Real;
|
||||
|
||||
public Vector2 Anchor;
|
||||
public Vector2 StaticPointAnchor;
|
||||
public Vector2 AnchorDif;
|
||||
|
||||
public CUINullRect Absolute;
|
||||
|
||||
public CUISprite Sprite;
|
||||
public Vector2 MemoStaticPoint;
|
||||
|
||||
public bool Grabbed;
|
||||
public bool Visible = false;
|
||||
|
||||
public CUIBool2 Direction { get; set; } = new CUIBool2(true, true);
|
||||
|
||||
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||
|
||||
public bool ShouldStart(CUIInput input)
|
||||
{
|
||||
return Visible && Real.Contains(input.MousePosition) && (
|
||||
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||
);
|
||||
}
|
||||
|
||||
public void BeginResize(Vector2 cursorPos)
|
||||
{
|
||||
Grabbed = true;
|
||||
MemoStaticPoint = CUIAnchor.PosIn(Host.Real, StaticPointAnchor);
|
||||
}
|
||||
|
||||
public void EndResize()
|
||||
{
|
||||
Grabbed = false;
|
||||
Host.MainComponent?.OnResizeEnd(this);
|
||||
}
|
||||
|
||||
public void Resize(Vector2 cursorPos)
|
||||
{
|
||||
float limitedX;
|
||||
if (CUIAnchor.Direction(StaticPointAnchor).X >= 0)
|
||||
{
|
||||
limitedX = Math.Max(MemoStaticPoint.X + Real.Width, cursorPos.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
limitedX = Math.Min(MemoStaticPoint.X - Real.Width, cursorPos.X);
|
||||
}
|
||||
float limitedY;
|
||||
if (CUIAnchor.Direction(StaticPointAnchor).Y >= 0)
|
||||
{
|
||||
limitedY = Math.Max(MemoStaticPoint.Y + Real.Height, cursorPos.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
limitedY = Math.Min(MemoStaticPoint.Y - Real.Height, cursorPos.Y);
|
||||
}
|
||||
|
||||
Vector2 LimitedCursorPos = new Vector2(limitedX, limitedY);
|
||||
|
||||
|
||||
Vector2 RealDif = MemoStaticPoint - LimitedCursorPos;
|
||||
Vector2 SizeFactor = RealDif / AnchorDif;
|
||||
Vector2 TopLeft = MemoStaticPoint - SizeFactor * StaticPointAnchor;
|
||||
|
||||
|
||||
Vector2 newSize = new Vector2(
|
||||
Math.Max(Real.Width, SizeFactor.X),
|
||||
Math.Max(Real.Height, SizeFactor.Y)
|
||||
);
|
||||
|
||||
Vector2 newPos = TopLeft - CUIAnchor.PosIn(Host.Parent.Real, Host.ParentAnchor ?? Host.Anchor) + CUIAnchor.PosIn(new CUIRect(newSize), Host.Anchor);
|
||||
|
||||
if (Direction.X) Host.CUIProps.Absolute.SetValue(new CUINullRect(newPos.X, Host.Absolute.Top, newSize.X, Host.Absolute.Height));
|
||||
if (Direction.Y) Host.CUIProps.Absolute.SetValue(new CUINullRect(Host.Absolute.Left, newPos.Y, Host.Absolute.Width, newSize.Y));
|
||||
}
|
||||
public void Update()
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
float x, y, w, h;
|
||||
x = y = w = h = 0;
|
||||
|
||||
if (Absolute.Left.HasValue) x = Absolute.Left.Value;
|
||||
if (Absolute.Top.HasValue) y = Absolute.Top.Value;
|
||||
if (Absolute.Width.HasValue) w = Absolute.Width.Value;
|
||||
if (Absolute.Height.HasValue) h = Absolute.Height.Value;
|
||||
|
||||
Vector2 Pos = CUIAnchor.GetChildPos(Host.Real, Anchor, new Vector2(x, y), new Vector2(w, h));
|
||||
|
||||
Real = new CUIRect(Pos, new Vector2(w, h));
|
||||
}
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
CUI.DrawRectangle(spriteBatch, Real, Grabbed ? Host.ResizeHandleGrabbedColor : Host.ResizeHandleColor, Sprite);
|
||||
}
|
||||
|
||||
public CUIResizeHandle(Vector2 anchor, CUIBool2 flipped)
|
||||
{
|
||||
if (anchor == CUIAnchor.Center)
|
||||
{
|
||||
CUI.Log($"Pls don't use CUIAnchor.Center for CUIResizeHandle, it makes no sense:\nThe StaticPointAnchor is symetric to Anchor and in this edge case == Anchor");
|
||||
}
|
||||
|
||||
Anchor = anchor;
|
||||
StaticPointAnchor = Vector2.One - Anchor;
|
||||
AnchorDif = StaticPointAnchor - Anchor;
|
||||
|
||||
Absolute = new CUINullRect(0, 0, 15, 15);
|
||||
Sprite = CUI.TextureManager.GetSprite(CUI.CUITexturePath);
|
||||
Sprite.SourceRect = new Rectangle(0, 32, 32, 32);
|
||||
if (flipped.X) Sprite.Effects |= SpriteEffects.FlipHorizontally;
|
||||
if (flipped.Y) Sprite.Effects |= SpriteEffects.FlipVertically;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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
|
||||
{
|
||||
public class CUISwipeHandle : ICUIVitalizable
|
||||
{
|
||||
public void SetHost(CUIComponent host) => Host = host;
|
||||
public CUIComponent Host;
|
||||
public bool Grabbed;
|
||||
public bool Swipeable;
|
||||
public Vector2 PrevPosition;
|
||||
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||
public bool ShouldStart(CUIInput input)
|
||||
{
|
||||
return Swipeable && (
|
||||
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||
);
|
||||
}
|
||||
|
||||
public void BeginSwipe(Vector2 cursorPos)
|
||||
{
|
||||
Grabbed = true;
|
||||
PrevPosition = cursorPos;
|
||||
}
|
||||
|
||||
public void EndSwipe()
|
||||
{
|
||||
Grabbed = false;
|
||||
Host.MainComponent?.OnSwipeEnd(this);
|
||||
}
|
||||
|
||||
public void Swipe(CUIInput input)
|
||||
{
|
||||
Host.CUIProps.ChildrenOffset.SetValue(
|
||||
Host.ChildrenOffset.Shift(
|
||||
input.MousePositionDif.X,
|
||||
input.MousePositionDif.Y
|
||||
)
|
||||
);
|
||||
Host.InvokeOnSwipe(input.MousePositionDif.X, input.MousePositionDif.Y);
|
||||
}
|
||||
public CUISwipeHandle() { }
|
||||
|
||||
public CUISwipeHandle(CUIComponent host) => Host = host;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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
|
||||
{
|
||||
|
||||
public class CUIWeakEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.IO;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUI
|
||||
{
|
||||
/// <summary>
|
||||
/// $"‖color:{color}‖{msg}‖end‖"
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="color"></param>
|
||||
/// <returns></returns>
|
||||
public static string WrapInColor(object msg, string color)
|
||||
{
|
||||
return $"‖color:{color}‖{msg}‖end‖";
|
||||
}
|
||||
|
||||
//HACK too lazy to make good name
|
||||
/// <summary>
|
||||
/// Serializes the array
|
||||
/// </summary>
|
||||
/// <param name="array"></param>
|
||||
/// <returns></returns>
|
||||
public static string ArrayToString(IEnumerable<object> array)
|
||||
{
|
||||
return $"[{String.Join(", ", array.Select(o => o.ToString()))}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a message to console
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="color"></param>
|
||||
public static void Log(object msg, Color? color = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
color ??= Color.Cyan;
|
||||
|
||||
// var fi = new FileInfo(source);
|
||||
// LuaCsLogger.LogMessage($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", color * 0.6f, color * 0.6f);
|
||||
|
||||
LuaCsLogger.LogMessage($"{msg ?? "null"}", color * 0.8f, color);
|
||||
}
|
||||
|
||||
public static void Warning(object msg, Color? color = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
color ??= Color.Yellow;
|
||||
// var fi = new FileInfo(source);
|
||||
// LuaCsLogger.LogMessage($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", color * 0.6f, color * 0.6f);
|
||||
LuaCsLogger.LogMessage($"{msg ?? "null"}", color * 0.8f, color);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// xd
|
||||
/// </summary>
|
||||
/// <param name="source"> This should be injected by compiler, don't set </param>
|
||||
/// <returns></returns>
|
||||
public static string GetCallerFolderPath([CallerFilePath] string source = "") => Path.GetDirectoryName(source);
|
||||
|
||||
/// <summary>
|
||||
/// Prints debug message with source path
|
||||
/// Works only if debug is true
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public static void Info(object msg, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
if (Debug == true)
|
||||
{
|
||||
var fi = new FileInfo(source);
|
||||
|
||||
Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", Color.Yellow * 0.5f);
|
||||
Log(msg, Color.Yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
218
Quick Interactions/CSharp/Client/CrabUI/Global/CUI Patches.cs
Normal file
218
Quick Interactions/CSharp/Client/CrabUI/Global/CUI Patches.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
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 HarmonyLib;
|
||||
using EventInput;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUI
|
||||
{
|
||||
public static void CheckOtherPatches(string msg = "")
|
||||
{
|
||||
CUI.Log(msg);
|
||||
CUI.Log($"Harmony.GetAllPatchedMethods:", Color.Lime);
|
||||
foreach (MethodBase mb in Harmony.GetAllPatchedMethods())
|
||||
{
|
||||
Patches patches = Harmony.GetPatchInfo(mb);
|
||||
|
||||
if (patches.Prefixes.Count() > 0 || patches.Postfixes.Count() > 0)
|
||||
{
|
||||
CUI.Log($"{mb.DeclaringType}.{mb.Name}:");
|
||||
if (patches.Prefixes.Count() > 0)
|
||||
{
|
||||
CUI.Log($" Prefixes:");
|
||||
foreach (Patch patch in patches.Prefixes) { CUI.Log($" {patch.owner}"); }
|
||||
}
|
||||
|
||||
if (patches.Postfixes.Count() > 0)
|
||||
{
|
||||
CUI.Log($" Postfixes:");
|
||||
foreach (Patch patch in patches.Postfixes) { CUI.Log($" {patch.owner}"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CheckPatches(string typeName, string methodName)
|
||||
{
|
||||
CUI.Log($"Harmony.GetAllPatchedMethods:", Color.Lime);
|
||||
foreach (MethodBase mb in Harmony.GetAllPatchedMethods())
|
||||
{
|
||||
if (
|
||||
!string.Equals(typeName, mb.DeclaringType.Name, StringComparison.OrdinalIgnoreCase) ||
|
||||
!string.Equals(methodName, mb.Name, StringComparison.OrdinalIgnoreCase)
|
||||
) continue;
|
||||
|
||||
Patches patches = Harmony.GetPatchInfo(mb);
|
||||
|
||||
if (patches.Prefixes.Count() > 0 || patches.Postfixes.Count() > 0)
|
||||
{
|
||||
CUI.Log($"{mb.DeclaringType}.{mb.Name}:");
|
||||
if (patches.Prefixes.Count() > 0)
|
||||
{
|
||||
CUI.Log($" Prefixes:");
|
||||
foreach (Patch patch in patches.Prefixes) { CUI.Log($" {patch.owner}"); }
|
||||
}
|
||||
|
||||
if (patches.Postfixes.Count() > 0)
|
||||
{
|
||||
CUI.Log($" Postfixes:");
|
||||
foreach (Patch patch in patches.Postfixes) { CUI.Log($" {patch.owner}"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void PatchAll()
|
||||
{
|
||||
GameMain.LuaCs.Hook.Add("GUI_Draw_Prefix", CUIHookID, (object[] args) =>
|
||||
{
|
||||
GUI_Draw_Prefix((SpriteBatch)args.ElementAtOrDefault(0));
|
||||
return null;
|
||||
});
|
||||
|
||||
GameMain.LuaCs.Hook.Add("GUI_DrawCursor_Prefix", CUIHookID, (object[] args) =>
|
||||
{
|
||||
GUI_DrawCursor_Prefix((SpriteBatch)args.ElementAtOrDefault(0));
|
||||
return null;
|
||||
});
|
||||
|
||||
GameMain.LuaCs.Hook.Add("think", CUIHookID, (object[] args) =>
|
||||
{
|
||||
CUIUpdateMouseOn();
|
||||
CUIUpdate(Timing.TotalTime);
|
||||
return null;
|
||||
});
|
||||
|
||||
// this hook seems to do nothing
|
||||
// GameMain.LuaCs.Hook.Add("Camera_MoveCamera_Prefix", CUIHookID, (object[] args) =>
|
||||
// {
|
||||
// return Camera_MoveCamera_Prefix(); ;
|
||||
// });
|
||||
|
||||
GameMain.LuaCs.Hook.Add("KeyboardDispatcher_set_Subscriber_Prefix", CUIHookID, (object[] args) =>
|
||||
{
|
||||
KeyboardDispatcher_set_Subscriber_Prefix(
|
||||
(KeyboardDispatcher)args.ElementAtOrDefault(0),
|
||||
(IKeyboardSubscriber)args.ElementAtOrDefault(1)
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
GameMain.LuaCs.Hook.Add("GUI_InputBlockingMenuOpen_Postfix", CUIHookID, (object[] args) =>
|
||||
{
|
||||
return GUI_InputBlockingMenuOpen_Postfix();
|
||||
});
|
||||
|
||||
GameMain.LuaCs.Hook.Add("GUI_TogglePauseMenu_Postfix", CUIHookID, (object[] args) =>
|
||||
{
|
||||
GUI_TogglePauseMenu_Postfix();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static void GameMain_Update_Postfix(GameTime gameTime)
|
||||
{
|
||||
CUIUpdate(gameTime.TotalGameTime.TotalSeconds);
|
||||
}
|
||||
private static void CUIUpdate(double time)
|
||||
{
|
||||
if (Main == null) CUI.Error($"CUIUpdate: CUI.Main in {HookIdentifier} was null, tell the dev", 20);
|
||||
try
|
||||
{
|
||||
CUIAnimation.UpdateAllAnimations(time);
|
||||
CUI.Input?.Scan(time);
|
||||
TopMain?.Update(time);
|
||||
Main?.Update(time);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"CUI: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void GUI_Draw_Prefix(SpriteBatch spriteBatch)
|
||||
{
|
||||
try { Main?.Draw(spriteBatch); }
|
||||
catch (Exception e) { CUI.Warning($"CUI: {e}"); }
|
||||
}
|
||||
|
||||
private static void GUI_DrawCursor_Prefix(SpriteBatch spriteBatch)
|
||||
{
|
||||
try { TopMain?.Draw(spriteBatch); }
|
||||
catch (Exception e) { CUI.Warning($"CUI: {e}"); }
|
||||
}
|
||||
|
||||
private static void GUI_UpdateMouseOn_Postfix(ref GUIComponent __result)
|
||||
{
|
||||
CUIUpdateMouseOn();
|
||||
}
|
||||
|
||||
private static void CUIUpdateMouseOn()
|
||||
{
|
||||
if (Main == null) CUI.Error($"CUIUpdateMouseOn: CUI.Main in {HookIdentifier} was null, tell the dev", 20);
|
||||
if (GUI.MouseOn == null && Main != null && Main.MouseOn != null && Main.MouseOn != Main) GUI.MouseOn = CUIComponent.dummyComponent;
|
||||
if (TopMain != null && TopMain.MouseOn != null && TopMain.MouseOn != TopMain) GUI.MouseOn = CUIComponent.dummyComponent;
|
||||
}
|
||||
|
||||
private static Dictionary<string, bool> Camera_MoveCamera_Prefix()
|
||||
{
|
||||
if (GUI.MouseOn != CUIComponent.dummyComponent) return null;
|
||||
|
||||
return new Dictionary<string, bool>()
|
||||
{
|
||||
["allowZoom"] = false,
|
||||
};
|
||||
}
|
||||
|
||||
private static void KeyboardDispatcher_set_Subscriber_Prefix(KeyboardDispatcher __instance, IKeyboardSubscriber value)
|
||||
{
|
||||
FocusResolver?.OnVanillaIKeyboardSubscriberSet(value);
|
||||
}
|
||||
|
||||
public static bool GUI_InputBlockingMenuOpen_Postfix()
|
||||
{
|
||||
return CUI.InputBlockingMenuOpen;
|
||||
}
|
||||
|
||||
public static void GUI_TogglePauseMenu_Postfix()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (GUI.PauseMenu != null)
|
||||
{
|
||||
GUIFrame frame = GUI.PauseMenu;
|
||||
GUIComponent pauseMenuInner = frame.GetChild(1);
|
||||
GUIComponent list = frame.GetChild(1).GetChild(0);
|
||||
GUIButton resumeButton = (GUIButton)list.GetChild(0);
|
||||
|
||||
GUIButton.OnClickedHandler oldHandler = resumeButton.OnClicked;
|
||||
|
||||
resumeButton.OnClicked = (GUIButton button, object obj) =>
|
||||
{
|
||||
bool guh = oldHandler(button, obj);
|
||||
CUI.InvokeOnPauseMenuToggled();
|
||||
return guh;
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception e) { CUI.Warning(e); }
|
||||
|
||||
CUI.InvokeOnPauseMenuToggled();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
29
Quick Interactions/CSharp/Client/CrabUI/Global/CUIBuilder.cs
Normal file
29
Quick Interactions/CSharp/Client/CrabUI/Global/CUIBuilder.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.IO;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUI
|
||||
{
|
||||
//Idk, not very usefull
|
||||
/// <summary>
|
||||
/// Just an experimant
|
||||
/// Creates empty CUIComponent from class name
|
||||
/// </summary>
|
||||
/// <param name="componentName"></param>
|
||||
/// <returns></returns>
|
||||
public static CUIComponent Create(string componentName)
|
||||
{
|
||||
return (CUIComponent)Activator.CreateInstance(CUIReflection.GetComponentTypeByName(componentName));
|
||||
}
|
||||
}
|
||||
}
|
||||
155
Quick Interactions/CSharp/Client/CrabUI/Global/CUICommands.cs
Normal file
155
Quick Interactions/CSharp/Client/CrabUI/Global/CUICommands.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
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 HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUI
|
||||
{
|
||||
internal static List<DebugConsole.Command> AddedCommands = new List<DebugConsole.Command>();
|
||||
internal static void AddCommands()
|
||||
{
|
||||
AddedCommands.Add(new DebugConsole.Command("cuidebug", "", CUIDebug_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuicreatepalette", "cuicreatepalette name frontcolor [backcolor]", CUICreatePalette_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuimg", "", CUIMG_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuidraworder", "", CUIDrawOrder_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuiprinttree", "", CUIPrintTree_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("printsprites", "", PrintSprites_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("printkeys", "", PrintSprites_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuipalette", "load palette as primary", Palette_Command, () => new string[][] { CUIPalette.LoadedPalettes.Keys.ToArray() }));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuipalettedemo", "", PaletteDemo_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuicreatepaletteset", "name primaty secondary tertiary quaternary", CUICreatePaletteSet_Command, () => new string[][] {
|
||||
new string[]{},
|
||||
CUIPalette.LoadedPalettes.Keys.ToArray(),
|
||||
CUIPalette.LoadedPalettes.Keys.ToArray(),
|
||||
CUIPalette.LoadedPalettes.Keys.ToArray(),
|
||||
CUIPalette.LoadedPalettes.Keys.ToArray(),
|
||||
}));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuiloadpaletteset", "", CUILoadPaletteSet_Command));
|
||||
AddedCommands.Add(new DebugConsole.Command("cuicreateluatypesfile", "", CUICreateLuaTypesFile_Command));
|
||||
|
||||
|
||||
DebugConsole.Commands.InsertRange(0, AddedCommands);
|
||||
}
|
||||
|
||||
public static void CUICreateLuaTypesFile_Command(string[] args)
|
||||
{
|
||||
CUI.LuaRegistrar.ConstructLuaStaticsFile();
|
||||
}
|
||||
|
||||
public static void CUIDebug_Command(string[] args)
|
||||
{
|
||||
if (CUIDebugWindow.Main == null)
|
||||
{
|
||||
CUIDebugWindow.Open();
|
||||
}
|
||||
else
|
||||
{
|
||||
CUIDebugWindow.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void CUIDrawOrder_Command(string[] args)
|
||||
{
|
||||
foreach (CUIComponent c in CUI.Main.Flat)
|
||||
{
|
||||
CUI.Log(c);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CUIPrintTree_Command(string[] args)
|
||||
{
|
||||
CUI.Main?.PrintTree();
|
||||
CUI.TopMain?.PrintTree();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void CUICreatePalette_Command(string[] args)
|
||||
{
|
||||
string name = args.ElementAtOrDefault(0);
|
||||
Color colorA = CUIExtensions.ParseColor((args.ElementAtOrDefault(1) ?? "white"));
|
||||
Color colorB = CUIExtensions.ParseColor((args.ElementAtOrDefault(2) ?? "black"));
|
||||
CUIPalette palette = CUIPalette.CreatePaletteFromColors(name, colorA, colorB);
|
||||
CUIPalette.Primary = palette;
|
||||
}
|
||||
|
||||
public static void CUICreatePaletteSet_Command(string[] args)
|
||||
{
|
||||
CUIPalette.SaveSet(
|
||||
args.ElementAtOrDefault(0),
|
||||
args.ElementAtOrDefault(1),
|
||||
args.ElementAtOrDefault(2),
|
||||
args.ElementAtOrDefault(3),
|
||||
args.ElementAtOrDefault(4)
|
||||
);
|
||||
}
|
||||
|
||||
public static void CUILoadPaletteSet_Command(string[] args)
|
||||
{
|
||||
CUIPalette.LoadSet(Path.Combine(CUIPalette.PaletteSetsPath, args.ElementAtOrDefault(0)));
|
||||
}
|
||||
|
||||
public static void CUIMG_Command(string[] args) => CUIMagnifyingGlass.ToggleEquip();
|
||||
|
||||
public static void PrintSprites_Command(string[] args)
|
||||
{
|
||||
foreach (GUIComponentStyle style in GUIStyle.ComponentStyles)
|
||||
{
|
||||
CUI.Log($"{style.Name} {style.Sprites.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void PrintKeysCommand(string[] args)
|
||||
{
|
||||
CUIDebug.PrintKeys = !CUIDebug.PrintKeys;
|
||||
|
||||
if (CUIDebug.PrintKeys)
|
||||
{
|
||||
var values = typeof(Keys).GetEnumValues();
|
||||
foreach (var v in values)
|
||||
{
|
||||
Log($"{(int)v} {v}");
|
||||
}
|
||||
Log("---------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
public static void PaletteDemo_Command(string[] args)
|
||||
{
|
||||
try { CUIPalette.PaletteDemo(); } catch (Exception e) { CUI.Warning(e); }
|
||||
}
|
||||
|
||||
public static void Palette_Command(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
CUIPalette palette = CUIPalette.LoadedPalettes?.GetValueOrDefault(args.ElementAtOrDefault(0) ?? "");
|
||||
if (palette != null) CUIPalette.Primary = palette;
|
||||
}
|
||||
catch (Exception e) { CUI.Warning(e); }
|
||||
}
|
||||
|
||||
|
||||
internal static void RemoveCommands()
|
||||
{
|
||||
AddedCommands.ForEach(c => DebugConsole.Commands.Remove(c));
|
||||
AddedCommands.Clear();
|
||||
}
|
||||
|
||||
// public static void PermitCommands(Identifier command, ref bool __result)
|
||||
// {
|
||||
// if (AddedCommands.Any(c => c.Names.Contains(command.Value))) __result = true;
|
||||
// }
|
||||
}
|
||||
}
|
||||
149
Quick Interactions/CSharp/Client/CrabUI/Global/CUIDrawing.cs
Normal file
149
Quick Interactions/CSharp/Client/CrabUI/Global/CUIDrawing.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.IO;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUI
|
||||
{
|
||||
public const float Pi2 = (float)(Math.PI / 2.0);
|
||||
|
||||
|
||||
public static SamplerState NoSmoothing = new SamplerState()
|
||||
{
|
||||
Filter = TextureFilter.Point,
|
||||
AddressU = TextureAddressMode.Clamp,
|
||||
AddressV = TextureAddressMode.Clamp,
|
||||
AddressW = TextureAddressMode.Clamp,
|
||||
BorderColor = Color.White,
|
||||
MaxAnisotropy = 4,
|
||||
MaxMipLevel = 0,
|
||||
MipMapLevelOfDetailBias = -0.8f,
|
||||
ComparisonFunction = CompareFunction.Never,
|
||||
FilterMode = TextureFilterMode.Default,
|
||||
};
|
||||
|
||||
public static void DrawTexture(SpriteBatch sb, CUIRect cuirect, Color cl, Texture2D texture, float depth = 0.0f)
|
||||
{
|
||||
Rectangle sourceRect = new Rectangle(0, 0, (int)cuirect.Width, (int)cuirect.Height);
|
||||
|
||||
sb.Draw(texture, cuirect.Box, sourceRect, cl, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
|
||||
}
|
||||
public static void DrawRectangle(SpriteBatch sb, CUIRect cuirect, Color cl, CUISprite sprite, float depth = 0.0f)
|
||||
{
|
||||
Rectangle sourceRect = sprite.DrawMode switch
|
||||
{
|
||||
CUISpriteDrawMode.Resize => sprite.SourceRect,
|
||||
CUISpriteDrawMode.Wrap => new Rectangle(0, 0, (int)cuirect.Width, (int)cuirect.Height),
|
||||
CUISpriteDrawMode.Static => cuirect.Box,
|
||||
CUISpriteDrawMode.StaticDeep => cuirect.Zoom(0.9f),
|
||||
_ => sprite.SourceRect,
|
||||
};
|
||||
|
||||
Rectangle rect = new Rectangle(
|
||||
(int)(cuirect.Left + sprite.Offset.X * cuirect.Width),
|
||||
(int)(cuirect.Top + sprite.Offset.Y * cuirect.Height),
|
||||
(int)(cuirect.Width),
|
||||
(int)(cuirect.Height)
|
||||
);
|
||||
|
||||
//rect = cuirect.Box;
|
||||
|
||||
sb.Draw(sprite.Texture, rect, sourceRect, cl, sprite.Rotation, sprite.Origin, sprite.Effects, depth);
|
||||
}
|
||||
|
||||
//TODO i can calculate those rects in advance
|
||||
public static void DrawBorders(SpriteBatch sb, CUIComponent component, float depth = 0.0f)
|
||||
{
|
||||
Texture2D texture = component.BorderSprite.Texture;
|
||||
Rectangle sourceRect = texture.Bounds;
|
||||
|
||||
Rectangle targetRect;
|
||||
Color cl;
|
||||
float rotation = 0.0f;
|
||||
float thickness = 1.0f;
|
||||
bool visible = false;
|
||||
|
||||
// Right
|
||||
visible = component.RigthBorder?.Visible ?? component.Border.Visible;
|
||||
thickness = component.RigthBorder?.Thickness ?? component.Border.Thickness;
|
||||
cl = component.RigthBorder?.Color ?? component.Border.Color;
|
||||
targetRect = CUIRect.CreateRect(
|
||||
component.Real.Left + component.Real.Width,
|
||||
component.Real.Top,
|
||||
component.Real.Height,
|
||||
thickness
|
||||
);
|
||||
sourceRect = CUIRect.CreateRect(
|
||||
0, 0,
|
||||
targetRect.Width, texture.Height
|
||||
);
|
||||
rotation = Pi2;
|
||||
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.None, depth);
|
||||
|
||||
//Left
|
||||
visible = component.LeftBorder?.Visible ?? component.Border.Visible;
|
||||
thickness = component.LeftBorder?.Thickness ?? component.Border.Thickness;
|
||||
cl = component.LeftBorder?.Color ?? component.Border.Color;
|
||||
targetRect = CUIRect.CreateRect(
|
||||
component.Real.Left + thickness,
|
||||
component.Real.Top,
|
||||
component.Real.Height,
|
||||
thickness
|
||||
);
|
||||
sourceRect = CUIRect.CreateRect(
|
||||
0, 0,
|
||||
targetRect.Width, texture.Height
|
||||
);
|
||||
rotation = Pi2;
|
||||
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.FlipVertically, depth);
|
||||
|
||||
|
||||
//Top
|
||||
visible = component.TopBorder?.Visible ?? component.Border.Visible;
|
||||
thickness = component.TopBorder?.Thickness ?? component.Border.Thickness;
|
||||
cl = component.TopBorder?.Color ?? component.Border.Color;
|
||||
targetRect = CUIRect.CreateRect(
|
||||
component.Real.Left,
|
||||
component.Real.Top,
|
||||
component.Real.Width,
|
||||
thickness
|
||||
);
|
||||
sourceRect = CUIRect.CreateRect(
|
||||
0, 0,
|
||||
targetRect.Width, texture.Height
|
||||
);
|
||||
rotation = 0.0f;
|
||||
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.None, depth);
|
||||
|
||||
|
||||
|
||||
//Bottom
|
||||
visible = component.BottomBorder?.Visible ?? component.Border.Visible;
|
||||
thickness = component.BottomBorder?.Thickness ?? component.Border.Thickness;
|
||||
cl = component.BottomBorder?.Color ?? component.Border.Color;
|
||||
targetRect = CUIRect.CreateRect(
|
||||
component.Real.Left,
|
||||
component.Real.Bottom - thickness,
|
||||
component.Real.Width,
|
||||
thickness
|
||||
);
|
||||
sourceRect = CUIRect.CreateRect(
|
||||
0, 0,
|
||||
targetRect.Width, texture.Height
|
||||
);
|
||||
rotation = 0;
|
||||
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.FlipVertically, depth);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Quick Interactions/CSharp/Client/CrabUI/Global/CUIErrors.cs
Normal file
27
Quick Interactions/CSharp/Client/CrabUI/Global/CUIErrors.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.IO;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUI
|
||||
{
|
||||
public static Dictionary<string, int> Errors = new();
|
||||
public static void Error(object msg, int maxPrints = 1, bool silent = false)
|
||||
{
|
||||
string s = $"{msg}";
|
||||
if (!Errors.ContainsKey(s)) Errors[s] = 1;
|
||||
else Errors[s] = Errors[s] + 1;
|
||||
if (silent) return;
|
||||
if (Errors[s] <= maxPrints) Log($"CUI: {s} x{Errors[s]}", Color.Orange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
// [CUIInternal]
|
||||
public static partial class CUIExtensions
|
||||
{
|
||||
public static Color RandomColor() => new Color(CUI.Random.Next(256), CUI.Random.Next(256), CUI.Random.Next(256));
|
||||
|
||||
public static Color GrayScale(int v) => new Color(v, v, v);
|
||||
|
||||
public static Color Mult(this Color cl, float f) => new Color((int)(cl.R * f), (int)(cl.G * f), (int)(cl.B * f), cl.A);
|
||||
public static Color To(this Color colorA, Color colorB, float f) => ToolBox.GradientLerp(f, new Color[] { colorA, colorB });
|
||||
|
||||
public static Dictionary<string, Color> GetShades(Color colorA, Color? colorB = null)
|
||||
{
|
||||
Color clB = colorB ?? Color.Black;
|
||||
|
||||
Dictionary<string, Color> shades = new();
|
||||
|
||||
float steps = 6.0f;
|
||||
|
||||
shades["0"] = colorA.To(clB, 0.0f / steps);
|
||||
shades["1"] = colorA.To(clB, 1.0f / steps);
|
||||
shades["2"] = colorA.To(clB, 2.0f / steps);
|
||||
shades["3"] = colorA.To(clB, 3.0f / steps);
|
||||
shades["4"] = colorA.To(clB, 4.0f / steps);
|
||||
shades["5"] = colorA.To(clB, 5.0f / steps);
|
||||
shades["6"] = colorA.To(clB, 6.0f / steps);
|
||||
|
||||
return shades;
|
||||
}
|
||||
|
||||
public static void GeneratePaletteFromColors(Color colorA, Color colorB)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
220
Quick Interactions/CSharp/Client/CrabUI/Global/CUIExtensions.cs
Normal file
220
Quick Interactions/CSharp/Client/CrabUI/Global/CUIExtensions.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
[CUIInternal]
|
||||
public static partial class CUIExtensions
|
||||
{
|
||||
public static int Fit(this int i, int bottom, int top) => Math.Max(bottom, Math.Min(i, top));
|
||||
public static Vector2 Rotate(this Vector2 v, float angle) => Vector2.Transform(v, Matrix.CreateRotationZ(angle));
|
||||
public static string SubstringSafe(this string s, int start)
|
||||
{
|
||||
try
|
||||
{
|
||||
int safeStart = start.Fit(0, s.Length);
|
||||
return s.Substring(safeStart, s.Length - safeStart);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Log($"SubstringSafe {e}");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
public static string SubstringSafe(this string s, int start, int length)
|
||||
{
|
||||
int end = (start + length).Fit(0, s.Length);
|
||||
int safeStart = start.Fit(0, s.Length);
|
||||
int safeLength = end - safeStart;
|
||||
try
|
||||
{
|
||||
return s.Substring(safeStart, safeLength);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Log($"SubstringSafe {e.Message}\ns:\"{s}\" start: {start}->{safeStart} end: {end} length: {length}->{safeLength} ", Color.Orange);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> ParseKVPairs(string raw)
|
||||
{
|
||||
Dictionary<string, string> props = new();
|
||||
|
||||
if (raw == null || raw == "") return props;
|
||||
|
||||
string content = raw.Split('{', '}')[1];
|
||||
|
||||
List<string> expressions = new();
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
int depth = 0;
|
||||
for (int i = 0; i < content.Length; i++)
|
||||
{
|
||||
char c = content[i];
|
||||
end = i;
|
||||
if (c == '[' || c == '{') depth++;
|
||||
if (c == ']' || c == '}') depth--;
|
||||
|
||||
if (depth <= 0 && c == ',')
|
||||
{
|
||||
expressions.Add(content.Substring(start, end - start));
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
expressions.Add(content.Substring(start, end - start));
|
||||
|
||||
var pairs = expressions.Select(s => s.Split(':').Select(sub => sub.Trim()).ToArray());
|
||||
|
||||
foreach (var pair in pairs) { props[pair[0].ToLower()] = pair[1]; }
|
||||
return props;
|
||||
}
|
||||
|
||||
public static string ColorToString(Color c) => $"{c.R},{c.G},{c.B},{c.A}";
|
||||
public static string Vector2ToString(Vector2 v) => $"[{v.X},{v.Y}]";
|
||||
public static string NullVector2ToString(Vector2? v) => v.HasValue ? $"[{v.Value.X},{v.Value.Y}]" : "null";
|
||||
public static string NullIntToString(int? i) => i.HasValue ? $"{i}" : "null";
|
||||
public static string RectangleToString(Rectangle r) => $"[{r.X},{r.Y},{r.Width},{r.Height}]";
|
||||
public static string GUIFontToString(GUIFont f) => f.Identifier.Value;
|
||||
public static string SpriteEffectsToString(SpriteEffects e)
|
||||
{
|
||||
if ((int)e == 3) return "FlipBothSides";
|
||||
else return e.ToString();
|
||||
}
|
||||
|
||||
public static string IEnumerableStringToString(IEnumerable<string> e) => $"[{string.Join(',', e.ToArray())}]";
|
||||
|
||||
public static IEnumerable<string> ParseIEnumerableString(string raw)
|
||||
{
|
||||
if (raw == null || raw == "") return new List<string>();
|
||||
string content = raw.Split('[', ']')[1];
|
||||
return content.Split(',');
|
||||
}
|
||||
|
||||
public static string ParseString(string s) => s; // BaroDev (wide)
|
||||
//public static GUISoundType ParseGUISoundType(string s) => Enum.Parse<GUISoundType>(s);
|
||||
|
||||
public static GUIFont ParseGUIFont(string raw)
|
||||
{
|
||||
GUIFont font = GUIStyle.Fonts.GetValueOrDefault(new Identifier(raw.Trim()));
|
||||
font ??= GUIStyle.Font;
|
||||
return font;
|
||||
}
|
||||
|
||||
public static SpriteEffects ParseSpriteEffects(string raw)
|
||||
{
|
||||
if (raw == "FlipBothSides") return SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically;
|
||||
else return Enum.Parse<SpriteEffects>(raw);
|
||||
}
|
||||
|
||||
|
||||
public static int? ParseNullInt(string raw)
|
||||
{
|
||||
if (raw == "null") return null;
|
||||
return int.Parse(raw);
|
||||
}
|
||||
public static Vector2? ParseNullVector2(string raw)
|
||||
{
|
||||
if (raw == "null") return null;
|
||||
return ParseVector2(raw);
|
||||
}
|
||||
|
||||
public static Vector2 ParseVector2(string raw)
|
||||
{
|
||||
if (raw == null || raw == "") return new Vector2(0, 0);
|
||||
|
||||
string content = raw.Split('[', ']')[1];
|
||||
|
||||
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
|
||||
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
|
||||
float.TryParse(coords.ElementAtOrDefault(0), out x);
|
||||
float.TryParse(coords.ElementAtOrDefault(1), out y);
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
public static Rectangle ParseRectangle(string raw)
|
||||
{
|
||||
if (raw == null || raw == "") return new Rectangle(0, 0, 1, 1);
|
||||
|
||||
string content = raw.Split('[', ']')[1];
|
||||
|
||||
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
|
||||
int.TryParse(coords.ElementAtOrDefault(0), out x);
|
||||
int.TryParse(coords.ElementAtOrDefault(1), out y);
|
||||
int.TryParse(coords.ElementAtOrDefault(2), out w);
|
||||
int.TryParse(coords.ElementAtOrDefault(3), out h);
|
||||
|
||||
return new Rectangle(x, y, w, h);
|
||||
}
|
||||
|
||||
|
||||
public static Color ParseColor(string s) => XMLExtensions.ParseColor(s, false);
|
||||
|
||||
|
||||
public static Dictionary<Type, MethodInfo> Parse;
|
||||
public static Dictionary<Type, MethodInfo> CustomToString;
|
||||
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () =>
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
||||
Parse = new Dictionary<Type, MethodInfo>();
|
||||
CustomToString = new Dictionary<Type, MethodInfo>();
|
||||
|
||||
Parse[typeof(string)] = typeof(CUIExtensions).GetMethod("ParseString");
|
||||
//Parse[typeof(GUISoundType)] = typeof(CUIExtensions).GetMethod("ParseGUISoundType");
|
||||
|
||||
Parse[typeof(Rectangle)] = typeof(CUIExtensions).GetMethod("ParseRectangle");
|
||||
Parse[typeof(GUIFont)] = typeof(CUIExtensions).GetMethod("ParseGUIFont");
|
||||
Parse[typeof(Vector2?)] = typeof(CUIExtensions).GetMethod("ParseNullVector2");
|
||||
Parse[typeof(Vector2)] = typeof(CUIExtensions).GetMethod("ParseVector2");
|
||||
Parse[typeof(SpriteEffects)] = typeof(CUIExtensions).GetMethod("ParseSpriteEffects");
|
||||
Parse[typeof(Color)] = typeof(CUIExtensions).GetMethod("ParseColor");
|
||||
Parse[typeof(int?)] = typeof(CUIExtensions).GetMethod("ParseNullInt");
|
||||
Parse[typeof(IEnumerable<string>)] = typeof(CUIExtensions).GetMethod("ParseIEnumerableString");
|
||||
|
||||
|
||||
CustomToString[typeof(IEnumerable<string>)] = typeof(CUIExtensions).GetMethod("IEnumerableStringToString");
|
||||
CustomToString[typeof(int?)] = typeof(CUIExtensions).GetMethod("NullIntToString");
|
||||
CustomToString[typeof(Color)] = typeof(CUIExtensions).GetMethod("ColorToString");
|
||||
CustomToString[typeof(SpriteEffects)] = typeof(CUIExtensions).GetMethod("SpriteEffectsToString");
|
||||
CustomToString[typeof(Vector2)] = typeof(CUIExtensions).GetMethod("Vector2ToString");
|
||||
CustomToString[typeof(Vector2?)] = typeof(CUIExtensions).GetMethod("NullVector2ToString");
|
||||
CustomToString[typeof(GUIFont)] = typeof(CUIExtensions).GetMethod("GUIFontToString");
|
||||
CustomToString[typeof(Rectangle)] = typeof(CUIExtensions).GetMethod("RectangleToString");
|
||||
|
||||
CUIDebug.Log($"CUIExtensions.Initialize took {sw.ElapsedMilliseconds}ms");
|
||||
};
|
||||
|
||||
CUI.OnDispose += () =>
|
||||
{
|
||||
Parse.Clear();
|
||||
CustomToString.Clear();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.IO;
|
||||
using EventInput;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class CUIFocusResolver
|
||||
{
|
||||
private CUIComponent focusedCUIComponent;
|
||||
public CUIComponent FocusedCUIComponent
|
||||
{
|
||||
get => focusedCUIComponent;
|
||||
set
|
||||
{
|
||||
CUIComponent oldFocused = focusedCUIComponent;
|
||||
CUIComponent newFocused = value;
|
||||
|
||||
if (oldFocused == newFocused) return;
|
||||
|
||||
if (oldFocused != null)
|
||||
{
|
||||
oldFocused.Focused = false;
|
||||
oldFocused.InvokeOnFocusLost();
|
||||
}
|
||||
|
||||
if (newFocused != null)
|
||||
{
|
||||
newFocused.Focused = true;
|
||||
newFocused.InvokeOnFocus();
|
||||
}
|
||||
|
||||
if (oldFocused is IKeyboardSubscriber || newFocused is null)
|
||||
{
|
||||
OnVanillaIKeyboardSubscriberSet(null, true);
|
||||
}
|
||||
|
||||
if (newFocused is IKeyboardSubscriber)
|
||||
{
|
||||
OnVanillaIKeyboardSubscriberSet((IKeyboardSubscriber)newFocused, true);
|
||||
}
|
||||
|
||||
focusedCUIComponent = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnVanillaIKeyboardSubscriberSet(IKeyboardSubscriber value, bool callFromCUI = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
KeyboardDispatcher _ = GUI.KeyboardDispatcher;
|
||||
|
||||
IKeyboardSubscriber oldSubscriber = _._subscriber;
|
||||
IKeyboardSubscriber newSubscriber = value;
|
||||
|
||||
if (newSubscriber == oldSubscriber) { return; }
|
||||
|
||||
// this case should be handled in CUI
|
||||
if (!callFromCUI && oldSubscriber is CUIComponent && newSubscriber is null) { return; }
|
||||
|
||||
//CUI.Log($"new IKeyboardSubscriber {oldSubscriber} -> {newSubscriber}");
|
||||
|
||||
if (oldSubscriber != null)
|
||||
{
|
||||
TextInput.StopTextInput();
|
||||
oldSubscriber.Selected = false;
|
||||
}
|
||||
|
||||
if (oldSubscriber is CUIComponent component && newSubscriber is GUITextBox)
|
||||
{
|
||||
//TODO for some season TextInput doesn't loose focus here
|
||||
component.InvokeOnFocusLost();
|
||||
component.Focused = false;
|
||||
focusedCUIComponent = null;
|
||||
}
|
||||
|
||||
if (newSubscriber != null)
|
||||
{
|
||||
if (newSubscriber is GUITextBox box)
|
||||
{
|
||||
TextInput.SetTextInputRect(box.MouseRect);
|
||||
TextInput.StartTextInput();
|
||||
TextInput.SetTextInputRect(box.MouseRect);
|
||||
}
|
||||
|
||||
if (newSubscriber is CUIComponent)
|
||||
{
|
||||
TextInput.StartTextInput();
|
||||
}
|
||||
|
||||
newSubscriber.Selected = true;
|
||||
}
|
||||
|
||||
_._subscriber = value;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#define USELUA
|
||||
|
||||
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 HarmonyLib;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class CUIInternalAttribute : System.Attribute { }
|
||||
[CUIInternal]
|
||||
public class CUILuaRegistrar
|
||||
{
|
||||
public static string CUITypesFile => Path.Combine(CUI.LuaFolder, "CUITypes.lua");
|
||||
|
||||
public static bool IsRealCUIType(Type T)
|
||||
{
|
||||
if (T.DeclaringType != null) return false; // nested type
|
||||
if (T.Name == "<>c") return false; // guh
|
||||
if (T.IsGenericType) return false; // in lua?
|
||||
if (T.IsInterface) return false;
|
||||
if (T.IsSubclassOf(typeof(Attribute))) return false;
|
||||
if (Attribute.IsDefined(T, typeof(CUIInternalAttribute))) return false;
|
||||
if (typeof(CUILuaRegistrar).Namespace != T.Namespace) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if !USELUA
|
||||
[Conditional("DONT")]
|
||||
#endif
|
||||
public void Register()
|
||||
{
|
||||
if (CUI.LuaFolder == null) return;
|
||||
if (!Directory.Exists(CUI.LuaFolder) || !CUI.UseLua) return;
|
||||
|
||||
Assembly thisAssembly = Assembly.GetAssembly(typeof(CUILuaRegistrar));
|
||||
|
||||
foreach (Type T in thisAssembly.GetTypes().Where(IsRealCUIType))
|
||||
{
|
||||
LuaUserData.RegisterType(T.FullName);
|
||||
// This has to be done in lua
|
||||
//GameMain.LuaCs.Lua.Globals[T.Name] = UserData.CreateStatic(T);
|
||||
}
|
||||
|
||||
GameMain.LuaCs.RegisterAction<CUIInput>();
|
||||
GameMain.LuaCs.RegisterAction<float, float>();
|
||||
GameMain.LuaCs.RegisterAction<TextInputEventArgs>();
|
||||
GameMain.LuaCs.RegisterAction<string>();
|
||||
GameMain.LuaCs.RegisterAction<CUIComponent>();
|
||||
GameMain.LuaCs.RegisterAction<bool>();
|
||||
GameMain.LuaCs.RegisterAction<CUIComponent, int>();
|
||||
|
||||
|
||||
LuaUserData.RegisterType(typeof(CUI).FullName);
|
||||
GameMain.LuaCs.Lua.Globals[nameof(CUI)] = UserData.CreateStatic(typeof(CUI));
|
||||
|
||||
ConstructLuaStaticsFile();
|
||||
}
|
||||
|
||||
#if !USELUA
|
||||
[Conditional("DONT")]
|
||||
#endif
|
||||
public void Deregister()
|
||||
{
|
||||
try
|
||||
{
|
||||
GameMain.LuaCs.Lua.Globals[nameof(CUI)] = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void ConstructLuaStaticsFile()
|
||||
{
|
||||
if (!Directory.Exists(CUI.LuaFolder)) return;
|
||||
|
||||
Assembly thisAssembly = Assembly.GetAssembly(typeof(CUILuaRegistrar));
|
||||
|
||||
string content = "-- This file is autogenerated\n";
|
||||
|
||||
foreach (Type T in thisAssembly.GetTypes().Where(IsRealCUIType))
|
||||
{
|
||||
content += $"{T.Name} = LuaUserData.CreateStatic('{T.FullName}', true)\n";
|
||||
}
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(CUITypesFile, false))
|
||||
{
|
||||
writer.Write(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.IO;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class CUIMultiModResolver
|
||||
{
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () =>
|
||||
{
|
||||
//FindOtherInputs();
|
||||
};
|
||||
CUI.OnDispose += () =>
|
||||
{
|
||||
CUIInputs.Clear();
|
||||
CUIs.Clear();
|
||||
MouseInputHandledMethods.Clear();
|
||||
};
|
||||
}
|
||||
|
||||
public static List<object> CUIInputs = new();
|
||||
public static List<object> CUIs = new();
|
||||
public static List<Action<bool>> MouseInputHandledMethods = new();
|
||||
|
||||
public static void MarkOtherInputsAsHandled()
|
||||
{
|
||||
//MouseInputHandledMethods.ForEach(action => action(true));
|
||||
|
||||
foreach (object input in CUIInputs)
|
||||
{
|
||||
try
|
||||
{
|
||||
PropertyInfo setAsHandled = input.GetType().GetProperty("MouseInputHandled");
|
||||
setAsHandled.SetValue(input, true);
|
||||
CUI.Log($"setAsHandled.SetValue(input, true) for {input}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Couldn't find MouseInputHandled in CUIInput in CUI from other mod ({input.GetType()})");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void FindOtherInputs()
|
||||
{
|
||||
AppDomain currentDomain = AppDomain.CurrentDomain;
|
||||
|
||||
foreach (Assembly asm in currentDomain.GetAssemblies())
|
||||
{
|
||||
foreach (Type T in asm.GetTypes())
|
||||
{
|
||||
if (T.Name == "CUI")
|
||||
{
|
||||
try
|
||||
{
|
||||
FieldInfo InstanceField = T.GetField("Instance", BindingFlags.Static | BindingFlags.Public);
|
||||
object CUIInstance = InstanceField.GetValue(null);
|
||||
if (CUIInstance != null && CUIInstance != CUI.Instance)
|
||||
{
|
||||
CUIs.Add(CUIInstance);
|
||||
FieldInfo inputField = T.GetField("input", AccessTools.all);
|
||||
|
||||
object input = inputField.GetValue(CUIInstance);
|
||||
if (input != null) CUIInputs.Add(input);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Couldn't find CUIInputs in CUI from other mod ({T})");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (object input in CUIInputs)
|
||||
{
|
||||
try
|
||||
{
|
||||
PropertyInfo setAsHandled = input.GetType().GetProperty("MouseInputHandled");
|
||||
MouseInputHandledMethods.Add(setAsHandled.SetMethod.CreateDelegate<Action<bool>>(input));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Couldn't find MouseInputHandled in CUIInput in CUI from other mod ({input.GetType()})");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
391
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPalette.cs
Normal file
391
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPalette.cs
Normal file
@@ -0,0 +1,391 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using HarmonyLib;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public enum PaletteOrder
|
||||
{
|
||||
Primary, Secondary, Tertiary, Quaternary
|
||||
}
|
||||
public record PaletteExtractResult(bool Ok, string Value = null);
|
||||
/// <summary>
|
||||
/// Contains abstract values that could be referenced in Styles
|
||||
/// </summary>
|
||||
public class CUIPalette
|
||||
{
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () =>
|
||||
{
|
||||
Initialize();
|
||||
};
|
||||
CUI.OnDispose += () =>
|
||||
{
|
||||
LoadedPalettes.Clear();
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString() => $"CUIPalette {Name}";
|
||||
public static string PaletteSetsPath => Path.Combine(CUI.PalettesPath, "Sets");
|
||||
public static string DefaultPalette = "Blue";
|
||||
|
||||
|
||||
//TODO why is it here? how could sane person find these?
|
||||
public static bool NotifyExcessivePropStyles { get; set; } = false;
|
||||
public static bool NotifiMissingPropStyles { get; set; } = true;
|
||||
|
||||
public static PaletteExtractResult Extract(string nestedName, PaletteOrder order)
|
||||
{
|
||||
CUIPalette palette = order switch
|
||||
{
|
||||
PaletteOrder.Primary => Primary,
|
||||
PaletteOrder.Secondary => Secondary,
|
||||
PaletteOrder.Tertiary => Tertiary,
|
||||
PaletteOrder.Quaternary => Quaternary,
|
||||
_ => Empty,
|
||||
};
|
||||
if (!palette.Values.ContainsKey(nestedName)) return new PaletteExtractResult(false);
|
||||
return new PaletteExtractResult(true, palette.Values[nestedName]);
|
||||
}
|
||||
|
||||
public static CUIPalette Empty => new CUIPalette();
|
||||
|
||||
public static Dictionary<string, CUIPalette> LoadedPalettes = new();
|
||||
public static string Default = "Blue";
|
||||
|
||||
private static CUIPalette primary = new CUIPalette();
|
||||
public static CUIPalette Primary
|
||||
{
|
||||
get => primary;
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
primary = value;
|
||||
CUIGlobalStyleResolver.OnPaletteChange(primary);
|
||||
}
|
||||
}
|
||||
|
||||
private static CUIPalette secondary = new CUIPalette();
|
||||
public static CUIPalette Secondary
|
||||
{
|
||||
get => secondary;
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
secondary = value;
|
||||
CUIGlobalStyleResolver.OnPaletteChange(secondary);
|
||||
}
|
||||
}
|
||||
|
||||
private static CUIPalette tertiary = new CUIPalette();
|
||||
public static CUIPalette Tertiary
|
||||
{
|
||||
get => tertiary;
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
tertiary = value;
|
||||
CUIGlobalStyleResolver.OnPaletteChange(tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
private static CUIPalette quaternary = new CUIPalette();
|
||||
public static CUIPalette Quaternary
|
||||
{
|
||||
get => quaternary;
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
quaternary = value;
|
||||
CUIGlobalStyleResolver.OnPaletteChange(quaternary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Dictionary<string, string> Values = new();
|
||||
public string Name = "???";
|
||||
public string BaseColor { get; set; } = "";
|
||||
|
||||
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
if (CUI.PalettesPath == null) return;
|
||||
|
||||
LoadedPalettes.Clear();
|
||||
|
||||
LoadPalettes();
|
||||
|
||||
LoadSet(Path.Combine(PaletteSetsPath, DefaultPalette + ".xml"));
|
||||
// Primary = LoadedPalettes.GetValueOrDefault("red");
|
||||
// Secondary = LoadedPalettes.GetValueOrDefault("purple");
|
||||
// Tertiary = LoadedPalettes.GetValueOrDefault("blue");
|
||||
// Quaternary = LoadedPalettes.GetValueOrDefault("cyan");
|
||||
|
||||
CUIDebug.Log($"CUIPalette.Initialize took {sw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
public static void LoadPalettes()
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(CUI.PalettesPath, "*.xml"))
|
||||
{
|
||||
CUIPalette palette = LoadFrom(file);
|
||||
LoadedPalettes[palette.Name] = palette;
|
||||
}
|
||||
}
|
||||
|
||||
public static CUIPalette FromXML(XElement root)
|
||||
{
|
||||
CUIPalette palette = new CUIPalette();
|
||||
|
||||
palette.Name = root.Attribute("Name")?.Value.ToString();
|
||||
|
||||
foreach (XElement element in root.Elements())
|
||||
{
|
||||
foreach (XAttribute attribute in element.Attributes())
|
||||
{
|
||||
palette.Values[$"{element.Name}.{attribute.Name}"] = attribute.Value;
|
||||
}
|
||||
|
||||
if (element.Value != "")
|
||||
{
|
||||
palette.Values[$"{element.Name}"] = element.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
public static CUIPalette LoadFrom(string path)
|
||||
{
|
||||
CUIPalette palette = new CUIPalette();
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
XElement root = xdoc.Root;
|
||||
|
||||
palette = CUIPalette.FromXML(root);
|
||||
palette.Name ??= Path.GetFileNameWithoutExtension(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Failed to load palette from {path}");
|
||||
CUI.Warning(e);
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
|
||||
public XElement ToXML()
|
||||
{
|
||||
XElement root = new XElement("Palette");
|
||||
root.Add(new XAttribute("Name", Name));
|
||||
root.Add(new XAttribute("BaseColor", BaseColor));
|
||||
|
||||
foreach (string key in Values.Keys)
|
||||
{
|
||||
string component = key.Split('.').ElementAtOrDefault(0);
|
||||
string prop = key.Split('.').ElementAtOrDefault(1);
|
||||
|
||||
if (component == null) continue;
|
||||
|
||||
if (root.Element(component) == null) root.Add(new XElement(component));
|
||||
|
||||
if (prop != null)
|
||||
{
|
||||
root.Element(component).Add(new XAttribute(prop, Values[key]));
|
||||
}
|
||||
else
|
||||
{
|
||||
root.Element(component).Value = Values[key];
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
public void SaveTo(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = new XDocument();
|
||||
xdoc.Add(this.ToXML());
|
||||
xdoc.Save(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Failed to save palette to {path}");
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void PaletteDemo()
|
||||
{
|
||||
if (CUI.AssetsPath == null)
|
||||
{
|
||||
CUI.Warning($"Can't load PaletteDemo, CUI.AssetsPath is null");
|
||||
return;
|
||||
}
|
||||
|
||||
void loadFrame(Vector2 offset, PaletteOrder order)
|
||||
{
|
||||
CUIFrame frame = CUIComponent.LoadFromFile<CUIFrame>(Path.Combine(CUI.AssetsPath, $"PaletteDemo.xml"));
|
||||
frame.DeepPalette = order;
|
||||
frame.Absolute = frame.Absolute with { Position = offset };
|
||||
frame.AddCommand("Close", (o) => frame.RemoveSelf());
|
||||
CUI.TopMain.Append(frame);
|
||||
}
|
||||
|
||||
loadFrame(new Vector2(0, 0), PaletteOrder.Primary);
|
||||
loadFrame(new Vector2(180, 0), PaletteOrder.Secondary);
|
||||
loadFrame(new Vector2(360, 0), PaletteOrder.Tertiary);
|
||||
loadFrame(new Vector2(540, 0), PaletteOrder.Quaternary);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static CUIPalette CreatePaletteFromColors(string name, Color colorA, Color? colorb = null)
|
||||
{
|
||||
CUIPalette palette = new CUIPalette()
|
||||
{
|
||||
Name = name,
|
||||
BaseColor = CUIExtensions.ColorToString(colorA),
|
||||
};
|
||||
|
||||
Color colorB = colorb ?? Color.Black;
|
||||
|
||||
Dictionary<string, Color> colors = new();
|
||||
|
||||
colors["Frame.Background"] = colorA.To(colorB, 1.0f);
|
||||
colors["Header.Background"] = colorA.To(colorB, 0.7f);
|
||||
colors["Nav.Background"] = colorA.To(colorB, 0.8f);
|
||||
colors["Main.Background"] = colorA.To(colorB, 0.9f);
|
||||
|
||||
colors["Frame.Border"] = colorA.To(colorB, 0.5f);
|
||||
colors["Header.Border"] = colorA.To(colorB, 0.6f);
|
||||
colors["Nav.Border"] = colorA.To(colorB, 0.7f);
|
||||
colors["Main.Border"] = colorA.To(colorB, 0.8f);
|
||||
|
||||
colors["Frame.Text"] = colorA.To(Color.White, 0.9f);
|
||||
colors["Header.Text"] = colorA.To(Color.White, 0.9f);
|
||||
colors["Nav.Text"] = colorA.To(Color.White, 0.8f);
|
||||
colors["Main.Text"] = colorA.To(Color.White, 0.8f);
|
||||
|
||||
colors["Component.Background"] = Color.Transparent;
|
||||
colors["Component.Border"] = Color.Transparent;
|
||||
colors["Component.Text"] = colors["Main.Text"];
|
||||
colors["Button.Background"] = colorA.To(colorB, 0.0f);
|
||||
colors["Button.Border"] = colorA.To(colorB, 0.5f);
|
||||
colors["Button.Disabled"] = colorA.To(new Color(16, 16, 16), 0.8f);
|
||||
colors["CloseButton.Background"] = colorA.To(Color.White, 0.2f);
|
||||
colors["DDOption.Background"] = colors["Header.Background"];
|
||||
colors["DDOption.Border"] = colors["Main.Border"];
|
||||
colors["DDOption.Hover"] = colorA.To(colorB, 0.5f);
|
||||
colors["DDOption.Text"] = colors["Main.Text"];
|
||||
colors["Handle.Background"] = colorA.To(colorB, 0.5f).To(Color.White, 0.2f);
|
||||
colors["Handle.Grabbed"] = colorA.To(colorB, 0.0f).To(Color.White, 0.2f);
|
||||
colors["Slider"] = colorA.To(Color.White, 0.7f);
|
||||
colors["Input.Background"] = colors["Nav.Background"];
|
||||
colors["Input.Border"] = colors["Nav.Border"];
|
||||
colors["Input.Text"] = colors["Main.Text"];
|
||||
colors["Input.Focused"] = colorA;
|
||||
colors["Input.Invalid"] = Color.Red;
|
||||
colors["Input.Valid"] = Color.Lime;
|
||||
colors["Input.Selection"] = colorA.To(Color.White, 0.7f) * 0.5f;
|
||||
colors["Input.Caret"] = colorA.To(Color.White, 0.7f) * 0.5f;
|
||||
|
||||
foreach (var (key, cl) in colors)
|
||||
{
|
||||
palette.Values[key] = CUIExtensions.ColorToString(cl);
|
||||
}
|
||||
|
||||
palette.SaveTo(Path.Combine(CUI.PalettesPath, $"{name}.xml"));
|
||||
LoadedPalettes[name] = palette;
|
||||
CUI.Log($"Created {name} palette and saved it to {Path.Combine(CUI.PalettesPath, $"{name}.xml")}");
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs 4 palettes into 1 set
|
||||
/// </summary>
|
||||
/// <param name="setName"></param>
|
||||
/// <param name="primary"></param>
|
||||
/// <param name="secondary"></param>
|
||||
/// <param name="tertiary"></param>
|
||||
/// <param name="quaternary"></param>
|
||||
public static void SaveSet(string setName, string primary = "", string secondary = "", string tertiary = "", string quaternary = "")
|
||||
{
|
||||
if (setName == null || setName == "") return;
|
||||
|
||||
string savePath = Path.Combine(PaletteSetsPath, $"{setName}.xml");
|
||||
|
||||
try
|
||||
{
|
||||
XDocument xdoc = new XDocument(new XElement("PaletteSet"));
|
||||
XElement root = xdoc.Root;
|
||||
|
||||
root.Add(new XAttribute("Name", setName));
|
||||
|
||||
root.Add((LoadedPalettes.GetValueOrDefault(primary ?? "") ?? Primary).ToXML());
|
||||
root.Add((LoadedPalettes.GetValueOrDefault(secondary ?? "") ?? Secondary).ToXML());
|
||||
root.Add((LoadedPalettes.GetValueOrDefault(tertiary ?? "") ?? Tertiary).ToXML());
|
||||
root.Add((LoadedPalettes.GetValueOrDefault(quaternary ?? "") ?? Quaternary).ToXML());
|
||||
|
||||
xdoc.Save(savePath);
|
||||
|
||||
CUI.Log($"Created {setName} palette set and saved it to {savePath}");
|
||||
LoadSet(savePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Failed to save palette set to {savePath}");
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadSet(string path)
|
||||
{
|
||||
if (path == null || path == "") return;
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
XElement root = xdoc.Root;
|
||||
|
||||
List<CUIPalette> palettes = new();
|
||||
|
||||
foreach (XElement element in root.Elements("Palette"))
|
||||
{
|
||||
palettes.Add(CUIPalette.FromXML(element));
|
||||
}
|
||||
|
||||
Primary = palettes.ElementAtOrDefault(0) ?? Empty;
|
||||
Secondary = palettes.ElementAtOrDefault(1) ?? Empty;
|
||||
Tertiary = palettes.ElementAtOrDefault(2) ?? Empty;
|
||||
Quaternary = palettes.ElementAtOrDefault(3) ?? Empty;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Failed to load palette set from {path}");
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
238
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPrefab.cs
Normal file
238
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPrefab.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public static class CUIStylePrefab
|
||||
{
|
||||
public static CUIStyle FrameCaption => new CUIStyle()
|
||||
{
|
||||
{"BackgroundColor", "CUIPalette.Frame.Border"},
|
||||
{"Border", "CUIPalette.Frame.Border"},
|
||||
{"TextColor", "CUIPalette.Frame.Text"},
|
||||
};
|
||||
|
||||
public static CUIStyle Header => new CUIStyle()
|
||||
{
|
||||
{"BackgroundColor", "CUIPalette.Header.Background"},
|
||||
{"Border", "CUIPalette.Header.Border"},
|
||||
{"TextColor", "CUIPalette.Header.Text"},
|
||||
};
|
||||
|
||||
public static CUIStyle Nav => new CUIStyle()
|
||||
{
|
||||
{"BackgroundColor", "CUIPalette.Nav.Background"},
|
||||
{"Border", "CUIPalette.Nav.Border"},
|
||||
{"TextColor", "CUIPalette.Nav.Text"},
|
||||
};
|
||||
|
||||
public static CUIStyle Main => new CUIStyle()
|
||||
{
|
||||
{"BackgroundColor", "CUIPalette.Main.Background"},
|
||||
{"Border", "CUIPalette.Main.Border"},
|
||||
{"TextColor", "CUIPalette.Main.Text"},
|
||||
};
|
||||
}
|
||||
|
||||
//TODO all this stuff is too specific, there should be more flexible way
|
||||
public static class CUIPrefab
|
||||
{
|
||||
public static CUIFrame ListFrame()
|
||||
{
|
||||
CUIFrame frame = new CUIFrame();
|
||||
frame["list"] = new CUIVerticalList() { Relative = new CUINullRect(0, 0, 1, 1), };
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
public static CUIComponent WrapInGroup(string name, CUIComponent content)
|
||||
{
|
||||
CUIVerticalList group = new CUIVerticalList() { FitContent = new CUIBool2(false, true), };
|
||||
group["header"] = new CUITextBlock(name)
|
||||
{
|
||||
TextScale = 1.0f,
|
||||
TextAlign = CUIAnchor.Center,
|
||||
};
|
||||
group["content"] = content;
|
||||
return group;
|
||||
}
|
||||
|
||||
public static CUIComponent Group(string name)
|
||||
{
|
||||
CUIVerticalList group = new CUIVerticalList()
|
||||
{
|
||||
FitContent = new CUIBool2(false, true),
|
||||
};
|
||||
|
||||
group["header"] = new CUITextBlock(name)
|
||||
{
|
||||
TextScale = 1.0f,
|
||||
TextAlign = CUIAnchor.Center,
|
||||
};
|
||||
|
||||
group["content"] = new CUIVerticalList()
|
||||
{
|
||||
FitContent = new CUIBool2(false, true),
|
||||
};
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public static CUIComponent TextAndSliderWithLabel(string name, string command, FloatRange? range = null)
|
||||
{
|
||||
CUIComponent wrapper = new CUIVerticalList()
|
||||
{
|
||||
FitContent = new CUIBool2(false, true),
|
||||
Style = CUIStylePrefab.Main,
|
||||
};
|
||||
|
||||
wrapper["label"] = new CUITextBlock(name);
|
||||
wrapper["controls"] = TextAndSlider(command, range);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public static CUIComponent TextAndSlider(string command, FloatRange? range = null)
|
||||
{
|
||||
CUIHorizontalList controls = new CUIHorizontalList()
|
||||
{
|
||||
FitContent = new CUIBool2(false, true),
|
||||
RetranslateCommands = true,
|
||||
ReflectCommands = true,
|
||||
};
|
||||
|
||||
controls["text"] = new CUITextInput()
|
||||
{
|
||||
Absolute = new CUINullRect(w: 20.0f),
|
||||
Consumes = command,
|
||||
Command = command,
|
||||
};
|
||||
controls["slider"] = new CUISlider()
|
||||
{
|
||||
Relative = new CUINullRect(h: 1.0f),
|
||||
FillEmptySpace = new CUIBool2(true, false),
|
||||
Consumes = command,
|
||||
Command = command,
|
||||
Range = range ?? new FloatRange(0, 1),
|
||||
};
|
||||
|
||||
return controls;
|
||||
}
|
||||
|
||||
public static CUIFrame ListFrameWithHeader()
|
||||
{
|
||||
CUIFrame frame = new CUIFrame() { };
|
||||
frame["layout"] = new CUIVerticalList() { Relative = new CUINullRect(0, 0, 1, 1), };
|
||||
frame["layout"]["handle"] = new CUIHorizontalList()
|
||||
{
|
||||
FitContent = new CUIBool2(false, true),
|
||||
Direction = CUIDirection.Reverse,
|
||||
Style = CUIStylePrefab.FrameCaption,
|
||||
};
|
||||
|
||||
frame["layout"]["handle"]["close"] = new CUICloseButton()
|
||||
{
|
||||
Absolute = new CUINullRect(0, 0, 15, 15),
|
||||
Command = "close frame",
|
||||
};
|
||||
frame["layout"]["handle"]["caption"] = new CUITextBlock("Caption") { FillEmptySpace = new CUIBool2(true, false) };
|
||||
frame["layout"]["header"] = new CUIHorizontalList()
|
||||
{
|
||||
FitContent = new CUIBool2(false, true),
|
||||
Style = CUIStylePrefab.Header,
|
||||
};
|
||||
frame["layout"]["content"] = new CUIVerticalList()
|
||||
{
|
||||
FillEmptySpace = new CUIBool2(false, true),
|
||||
Style = CUIStylePrefab.Main,
|
||||
Scrollable = true,
|
||||
ConsumeDragAndDrop = true,
|
||||
ConsumeMouseClicks = true,
|
||||
};
|
||||
|
||||
frame["header"] = frame["layout"]["header"];
|
||||
frame["content"] = frame["layout"]["content"];
|
||||
frame["caption"] = frame["layout"]["handle"]["caption"];
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static CUIHorizontalList TickboxWithLabel(string text, string command, float tickboxSize = 22.0f)
|
||||
{
|
||||
CUIHorizontalList list = new CUIHorizontalList()
|
||||
{
|
||||
FitContent = new CUIBool2(true, true),
|
||||
};
|
||||
|
||||
list["tickbox"] = new CUITickBox()
|
||||
{
|
||||
Absolute = new CUINullRect(w: tickboxSize, h: tickboxSize),
|
||||
Command = command,
|
||||
Consumes = command,
|
||||
};
|
||||
|
||||
list["text"] = new CUITextBlock()
|
||||
{
|
||||
Text = text,
|
||||
TextAlign = CUIAnchor.CenterLeft,
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
//TODO this is now too specific and shouldn't be here
|
||||
public static CUIHorizontalList InputWithValidation(PropertyInfo pi, string command)
|
||||
{
|
||||
string ToUserFriendly(Type T)
|
||||
{
|
||||
if (T == typeof(bool)) return "Boolean";
|
||||
if (T == typeof(int)) return "Integer";
|
||||
if (T == typeof(float)) return "Float";
|
||||
return T.Name;
|
||||
}
|
||||
|
||||
CUIHorizontalList list = new CUIHorizontalList()
|
||||
{
|
||||
FitContent = new CUIBool2(true, true),
|
||||
Border = new CUIBorder(),
|
||||
};
|
||||
|
||||
list["input"] = new CUITextInput()
|
||||
{
|
||||
AbsoluteMin = new CUINullRect(w: 100),
|
||||
Relative = new CUINullRect(w: 0.3f),
|
||||
Command = command,
|
||||
Consumes = command,
|
||||
VatidationType = pi.PropertyType,
|
||||
};
|
||||
|
||||
list["label"] = new CUITextBlock()
|
||||
{
|
||||
FillEmptySpace = new CUIBool2(true, false),
|
||||
Text = $"{ToUserFriendly(pi.PropertyType)} {pi.Name}",
|
||||
TextAlign = CUIAnchor.CenterLeft,
|
||||
BackgroundSprite = new CUISprite("gradient.png"),
|
||||
|
||||
Style = new CUIStyle(){
|
||||
{"BackgroundColor", "CUIPalette.Text3.Background"},
|
||||
{"Border", "CUIPalette.Text3.Border"},
|
||||
{"TextColor", "CUIPalette.Text3.Text"},
|
||||
},
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
174
Quick Interactions/CSharp/Client/CrabUI/Global/CUIReflection.cs
Normal file
174
Quick Interactions/CSharp/Client/CrabUI/Global/CUIReflection.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
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 HarmonyLib;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
|
||||
public class TypeTreeNode
|
||||
{
|
||||
public Type T;
|
||||
public TypeTreeNode Parent;
|
||||
public List<TypeTreeNode> Children = new();
|
||||
public CUITypeMetaData Meta => CUITypeMetaData.Get(T);
|
||||
public void Add(TypeTreeNode child)
|
||||
{
|
||||
child.Parent = this;
|
||||
Children.Add(child);
|
||||
}
|
||||
public TypeTreeNode(Type t) => T = t;
|
||||
public override string ToString() => T?.ToString() ?? "null";
|
||||
public void RunRecursive(Action<TypeTreeNode> action)
|
||||
{
|
||||
action(this);
|
||||
foreach (TypeTreeNode child in Children)
|
||||
{
|
||||
child.RunRecursive(action);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
[CUIInternal]
|
||||
public class CUIReflection
|
||||
{
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () =>
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
FindCUITypes();
|
||||
FormCUITypeTree();
|
||||
CUIDebug.Log($"CUIReflection.Initialize took {sw.ElapsedMilliseconds}ms");
|
||||
};
|
||||
CUI.OnDispose += () =>
|
||||
{
|
||||
CUITypes.Clear();
|
||||
CUILayoutTypes.Clear();
|
||||
CUITypeTree.Clear();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public record TypePair(Type type, Type baseType);
|
||||
|
||||
public static Dictionary<Type, TypeTreeNode> CUITypeTree = new();
|
||||
|
||||
public static Dictionary<string, Type> CUILayoutTypes = new();
|
||||
public static Dictionary<string, Type> CUITypes = new Dictionary<string, Type>();
|
||||
|
||||
public static void FormCUITypeTree()
|
||||
{
|
||||
List<TypePair> Pustoe = CUITypes.Values.Select(t => new TypePair(t, t.BaseType)).ToList();
|
||||
List<TypePair> Porojnee = new List<TypePair>();
|
||||
|
||||
while (Pustoe.Count > 0)
|
||||
{
|
||||
Porojnee = new List<TypePair>();
|
||||
foreach (TypePair pair in Pustoe)
|
||||
{
|
||||
// Tree root CUIComponent
|
||||
if (pair.baseType == typeof(object))
|
||||
{
|
||||
CUITypeTree[pair.type] = new TypeTreeNode(pair.type);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Derived class
|
||||
if (CUITypeTree.ContainsKey(pair.baseType))
|
||||
{
|
||||
CUITypeTree[pair.type] = new TypeTreeNode(pair.type);
|
||||
CUITypeTree[pair.baseType].Add(CUITypeTree[pair.type]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Base class not in tree yet
|
||||
Porojnee.Add(pair);
|
||||
}
|
||||
|
||||
Pustoe.Clear();
|
||||
Pustoe = Porojnee;
|
||||
}
|
||||
|
||||
//foreach (TypeTreeNode node in CUITypeTree.Values) CUI.Log(node);
|
||||
}
|
||||
|
||||
public static void FindCUITypes()
|
||||
{
|
||||
Assembly CUIAssembly = Assembly.GetAssembly(typeof(CUI));
|
||||
Assembly CallingAssembly = Assembly.GetCallingAssembly();
|
||||
|
||||
CUITypes["CUIComponent"] = typeof(CUIComponent);
|
||||
CUILayoutTypes["CUILayout"] = typeof(CUILayout);
|
||||
|
||||
|
||||
//TODO Nested types might have same name, think how to separate them
|
||||
// lua and style resolver uses them
|
||||
// string name = t.Name;
|
||||
// if (t.DeclaringType != null) name = $"{t.DeclaringType.Name}.{t.Name}";
|
||||
// CUITypes[name] = t;
|
||||
foreach (Type t in CallingAssembly.GetTypes())
|
||||
{
|
||||
if (t.IsSubclassOf(typeof(CUIComponent))) CUITypes[t.Name] = t;
|
||||
if (t.IsSubclassOf(typeof(CUILayout))) CUILayoutTypes[t.Name] = t;
|
||||
}
|
||||
|
||||
foreach (Type t in CUIAssembly.GetTypes())
|
||||
{
|
||||
if (t.IsSubclassOf(typeof(CUIComponent))) CUITypes[t.Name] = t;
|
||||
if (t.IsSubclassOf(typeof(CUILayout))) CUILayoutTypes[t.Name] = t;
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetComponentTypeByName(string name)
|
||||
{
|
||||
return CUITypes.GetValueOrDefault(name);
|
||||
}
|
||||
|
||||
public static object GetDefault(object obj)
|
||||
{
|
||||
FieldInfo defField = obj.GetType().GetField("Default", BindingFlags.Static | BindingFlags.Public);
|
||||
if (defField == null) return null;
|
||||
return defField.GetValue(null);
|
||||
}
|
||||
|
||||
public static object GetNestedValue(object obj, string nestedName)
|
||||
{
|
||||
string[] names = nestedName.Split('.');
|
||||
|
||||
foreach (string name in names)
|
||||
{
|
||||
FieldInfo fi = obj.GetType().GetField(name, AccessTools.all);
|
||||
PropertyInfo pi = obj.GetType().GetProperty(name, AccessTools.all);
|
||||
|
||||
if (fi != null)
|
||||
{
|
||||
obj = fi.GetValue(obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pi != null)
|
||||
{
|
||||
obj = pi.GetValue(obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.IO;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for loading textures without duplicates
|
||||
/// Also disposes all loaded textures automatically
|
||||
/// </summary>
|
||||
public class CUITextureManager : IDisposable
|
||||
{
|
||||
// private static Texture2D backupTexture;
|
||||
// public static Texture2D BackupTexture
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (backupTexture == null)
|
||||
// {
|
||||
// backupTexture = new Texture2D(GameMain.Instance.GraphicsDevice, 1, 1);
|
||||
// backupTexture.SetData(new Color[] { Color.White });
|
||||
// }
|
||||
// return backupTexture;
|
||||
// }
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Path to additional PNGs, it can be set in CUI
|
||||
/// </summary>
|
||||
public string PGNAssets { get; set; }
|
||||
|
||||
public static Texture2D BackupTexture => GUI.WhiteTexture;
|
||||
public Dictionary<string, Texture2D> LoadedTextures = new();
|
||||
public void DisposeAllTextures()
|
||||
{
|
||||
foreach (Texture2D texture in LoadedTextures.Values)
|
||||
{
|
||||
if (texture == BackupTexture) continue;
|
||||
texture.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public string NormalizePath(string path)
|
||||
{
|
||||
return path; //TODO
|
||||
}
|
||||
|
||||
public CUISprite GetSprite(string path, Rectangle? sourceRect = null, CUISpriteDrawMode? drawMode = null, SpriteEffects? effects = null)
|
||||
{
|
||||
CUISprite sprite = new CUISprite(GetTexture(path))
|
||||
{
|
||||
Path = path,
|
||||
};
|
||||
|
||||
if (sourceRect.HasValue) sprite.SourceRect = sourceRect.Value;
|
||||
if (drawMode.HasValue) sprite.DrawMode = drawMode.Value;
|
||||
if (effects.HasValue) sprite.Effects = effects.Value;
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 32x32 square on (x,y) position from CUI.png
|
||||
/// </summary>
|
||||
public CUISprite GetCUISprite(int x, int y, CUISpriteDrawMode? drawMode = null, SpriteEffects? effects = null)
|
||||
{
|
||||
return GetSprite(CUI.CUITexturePath, new Rectangle(x * 32, y * 32, 32, 32), drawMode, effects);
|
||||
}
|
||||
|
||||
private Texture2D TryLoad(string path)
|
||||
{
|
||||
Texture2D texture = null;
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using (FileStream fs = File.OpenRead(path))
|
||||
{
|
||||
texture = Texture2D.FromStream(GameMain.Instance.GraphicsDevice, fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return texture;
|
||||
}
|
||||
|
||||
public Texture2D GetTexture(string path)
|
||||
{
|
||||
if (LoadedTextures == null)
|
||||
{
|
||||
CUI.Error($"LoadedTextures was null");
|
||||
return BackupTexture;
|
||||
}
|
||||
|
||||
if (LoadedTextures.ContainsKey(path)) return LoadedTextures[path];
|
||||
|
||||
Texture2D loaded = null;
|
||||
|
||||
if (CUI.AssetsPath != null) loaded ??= TryLoad(Path.Combine(CUI.AssetsPath, path));
|
||||
if (PGNAssets != null) loaded ??= TryLoad(Path.Combine(PGNAssets, path));
|
||||
loaded ??= TryLoad(path);
|
||||
if (loaded == null)
|
||||
{
|
||||
CUI.Warning($"Coudn't find {path} texture, setting it to backup texture");
|
||||
loaded ??= BackupTexture;
|
||||
if (PGNAssets == null)
|
||||
{
|
||||
CUI.Warning($"Note: It was loaded before CUI.PGNAssets was set");
|
||||
}
|
||||
}
|
||||
|
||||
LoadedTextures[path] = loaded;
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeAllTextures();
|
||||
|
||||
LoadedTextures.Clear();
|
||||
LoadedTextures = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class CUISerializableAttribute : System.Attribute { }
|
||||
public class DontSerializeAttribute : System.Attribute { }
|
||||
public class CalculatedAttribute : System.Attribute { }
|
||||
public class NoDefaultAttribute : System.Attribute { }
|
||||
|
||||
public enum CUIAttribute
|
||||
{
|
||||
CUISerializable,
|
||||
DontSerialize,
|
||||
Calculated,
|
||||
}
|
||||
|
||||
public class CUITypeMetaData
|
||||
{
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () =>
|
||||
{
|
||||
TypeMetaData = new Dictionary<Type, CUITypeMetaData>();
|
||||
};
|
||||
CUI.OnDispose += () =>
|
||||
{
|
||||
TypeMetaData.Clear();
|
||||
};
|
||||
}
|
||||
|
||||
public static Dictionary<Type, CUITypeMetaData> TypeMetaData;
|
||||
|
||||
public static CUITypeMetaData Get(Type type)
|
||||
{
|
||||
if (!TypeMetaData.ContainsKey(type)) new CUITypeMetaData(type);
|
||||
return TypeMetaData[type];
|
||||
}
|
||||
|
||||
public Type CUIType;
|
||||
|
||||
public CUIComponent Default;
|
||||
|
||||
public CUIStyle defaultStyle; public CUIStyle DefaultStyle
|
||||
{
|
||||
get => defaultStyle;
|
||||
set
|
||||
{
|
||||
if (defaultStyle == value) return;
|
||||
|
||||
if (defaultStyle != null)
|
||||
{
|
||||
defaultStyle.OnUse -= HandleStyleChange;
|
||||
defaultStyle.OnPropChanged -= HandleStylePropChange;
|
||||
}
|
||||
|
||||
defaultStyle = value;
|
||||
|
||||
if (defaultStyle != null)
|
||||
{
|
||||
defaultStyle.OnUse += HandleStyleChange;
|
||||
defaultStyle.OnPropChanged += HandleStylePropChange;
|
||||
}
|
||||
|
||||
HandleStyleChange(defaultStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStylePropChange(string key, string value)
|
||||
{
|
||||
CUIGlobalStyleResolver.OnDefaultStylePropChanged(CUIType, key);
|
||||
}
|
||||
private void HandleStyleChange(CUIStyle s)
|
||||
{
|
||||
CUIGlobalStyleResolver.OnDefaultStyleChanged(CUIType);
|
||||
}
|
||||
|
||||
public CUIStyle ResolvedDefaultStyle { get; set; }
|
||||
|
||||
|
||||
public SortedDictionary<string, PropertyInfo> Serializable = new();
|
||||
public SortedDictionary<string, PropertyInfo> Calculated = new();
|
||||
public SortedDictionary<string, PropertyInfo> Assignable = new();
|
||||
|
||||
public CUITypeMetaData(Type type)
|
||||
{
|
||||
TypeMetaData[type] = this; // !!!
|
||||
CUIType = type;
|
||||
|
||||
foreach (PropertyInfo pi in type.GetProperties(AccessTools.all))
|
||||
{
|
||||
if (Attribute.IsDefined(pi, typeof(CUISerializableAttribute)))
|
||||
{
|
||||
Serializable[pi.Name] = pi;
|
||||
}
|
||||
|
||||
if (Attribute.IsDefined(pi, typeof(CalculatedAttribute)))
|
||||
{
|
||||
Calculated[pi.Name] = pi;
|
||||
}
|
||||
|
||||
if (pi.CanWrite)
|
||||
{
|
||||
Assignable[pi.Name] = pi;
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
DefaultStyle = new CUIStyle();
|
||||
if (!Attribute.IsDefined(type, typeof(NoDefaultAttribute)))
|
||||
{
|
||||
Default = (CUIComponent)Activator.CreateInstance(type);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is System.MissingMethodException) return;
|
||||
|
||||
CUI.Warning($"In CUITypeMetaData {type}\n{e}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
49
Quick Interactions/CSharp/Client/CrabUI/Global/ModStorage.cs
Normal file
49
Quick Interactions/CSharp/Client/CrabUI/Global/ModStorage.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
using Barotrauma;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class ModStorage
|
||||
{
|
||||
private static Dictionary<string, object> GetOrCreateRepo()
|
||||
{
|
||||
if (GUI.Canvas.GUIComponent is not GUIButton)
|
||||
{
|
||||
GUI.Canvas.GUIComponent = new GUIButton(new RectTransform(new Point(0, 0)));
|
||||
}
|
||||
|
||||
if (GUI.Canvas.GUIComponent.UserData is not Dictionary<string, object>)
|
||||
{
|
||||
GUI.Canvas.GUIComponent.UserData = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
return (Dictionary<string, object>)GUI.Canvas.GUIComponent.UserData;
|
||||
}
|
||||
|
||||
public static object Get<TValue>(string key) => (TValue)Get(key);
|
||||
public static object Get(string key)
|
||||
{
|
||||
Dictionary<string, object> repo = GetOrCreateRepo();
|
||||
return repo.GetValueOrDefault(key);
|
||||
}
|
||||
public static void Set(string key, object value)
|
||||
{
|
||||
Dictionary<string, object> repo = GetOrCreateRepo();
|
||||
repo[key] = value;
|
||||
}
|
||||
public static bool Has(string key)
|
||||
{
|
||||
Dictionary<string, object> repo = GetOrCreateRepo();
|
||||
return repo.ContainsKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
180
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayout.cs
Normal file
180
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayout.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all layouts
|
||||
/// </summary>
|
||||
public class CUILayout
|
||||
{
|
||||
public CUIComponent Host;
|
||||
|
||||
|
||||
//NOTE: This looks ugly, but no matter how i try to isolate this logic it gets only uglier
|
||||
// i've been stuck here for too long so i'll just do this
|
||||
// and each update pattern in fact used only once, so i think no big deal
|
||||
|
||||
/// <summary>
|
||||
/// This is just for debug, don't use it
|
||||
/// </summary>
|
||||
public void ForceMarkUnchanged()
|
||||
{
|
||||
decorChanged = false;
|
||||
changed = false;
|
||||
absoluteChanged = false;
|
||||
|
||||
foreach (CUIComponent child in Host.Children)
|
||||
{
|
||||
child.Layout.ForceMarkUnchanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void propagateChangedDown()
|
||||
{
|
||||
changed = true;
|
||||
DecorChanged = true;
|
||||
foreach (CUIComponent child in Host.Children)
|
||||
{
|
||||
child.Layout.propagateChangedDown();
|
||||
}
|
||||
}
|
||||
protected bool changed = true; public bool Changed
|
||||
{
|
||||
get => changed;
|
||||
set
|
||||
{
|
||||
changed = value;
|
||||
if (value)
|
||||
{
|
||||
if (Host.Parent != null) Host.Parent.Layout.propagateChangedDown();
|
||||
else propagateChangedDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void propagateDecorChangedDown()
|
||||
{
|
||||
DecorChanged = true;
|
||||
foreach (CUIComponent child in Host.Children)
|
||||
{
|
||||
child.Layout.propagateDecorChangedDown();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// It doesn't optimize anything
|
||||
/// </summary>
|
||||
public bool SelfAndParentChanged
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
changed = true;
|
||||
DecorChanged = true;
|
||||
if (Host.Parent != null)
|
||||
{
|
||||
Host.Parent.Layout.changed = true;
|
||||
Host.Parent.Layout.propagateDecorChangedDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ChildChanged
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value) propagateChangedDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void propagateAbsoluteChangedUp()
|
||||
{
|
||||
absoluteChanged = true;
|
||||
Host.Parent?.Layout.propagateAbsoluteChangedUp();
|
||||
}
|
||||
protected bool absoluteChanged = true; public bool AbsoluteChanged
|
||||
{
|
||||
get => absoluteChanged;
|
||||
set
|
||||
{
|
||||
if (!value) absoluteChanged = false;
|
||||
if (value && Host.Parent != null) Host.Parent.Layout.absoluteChanged = true;
|
||||
//if (value && Host.Parent != null) Host.Parent.Layout.propagateAbsoluteChangedUp();
|
||||
}
|
||||
}
|
||||
protected bool decorChanged = true; public bool DecorChanged
|
||||
{
|
||||
get => decorChanged;
|
||||
set
|
||||
{
|
||||
decorChanged = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void Update()
|
||||
{
|
||||
if (Changed)
|
||||
{
|
||||
if (Host.CullChildren)
|
||||
{
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
c.CulledOut = !c.UnCullable && !c.Real.Intersect(Host.Real);
|
||||
}
|
||||
}
|
||||
|
||||
Changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void UpdateDecor()
|
||||
{
|
||||
if (DecorChanged)
|
||||
{
|
||||
Host.UpdatePseudoChildren();
|
||||
DecorChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void ResizeToContent()
|
||||
{
|
||||
if (AbsoluteChanged && (Host.FitContent.X || Host.FitContent.Y))
|
||||
{
|
||||
// do something
|
||||
}
|
||||
|
||||
AbsoluteChanged = false;
|
||||
}
|
||||
|
||||
public CUILayout() { }
|
||||
public CUILayout(CUIComponent host)
|
||||
{
|
||||
Host = host;
|
||||
}
|
||||
|
||||
public override string ToString() => this.GetType().Name;
|
||||
public static CUILayout Parse(string raw)
|
||||
{
|
||||
if (raw != null)
|
||||
{
|
||||
raw = raw.Trim();
|
||||
if (CUIReflection.CUILayoutTypes.ContainsKey(raw))
|
||||
{
|
||||
return (CUILayout)Activator.CreateInstance(CUIReflection.CUILayoutTypes[raw]);
|
||||
}
|
||||
}
|
||||
return new CUILayoutSimple();
|
||||
}
|
||||
}
|
||||
}
|
||||
182
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayoutGrid.cs
Normal file
182
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayoutGrid.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
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>
|
||||
/// A Grid containing children in its cells
|
||||
/// Dividing host into rows and columns basing on GridTemplate
|
||||
/// And then placing children basin on their GridCell
|
||||
/// </summary>
|
||||
public class CUILayoutGrid : CUILayout
|
||||
{
|
||||
public enum TrackSizeType
|
||||
{
|
||||
Unknown,
|
||||
Absolute,
|
||||
Relative,
|
||||
Fractional,
|
||||
}
|
||||
|
||||
public class GridTrack
|
||||
{
|
||||
public TrackSizeType Type;
|
||||
public float? Absolute;
|
||||
public float? Relative;
|
||||
public float? Fractional;
|
||||
public float Start;
|
||||
public float End;
|
||||
public float Size;
|
||||
|
||||
public float RealSize(float hostSize)
|
||||
{
|
||||
float size = 0;
|
||||
|
||||
if (Absolute.HasValue) size = Absolute.Value;
|
||||
if (Relative.HasValue) size = Relative.Value * hostSize;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public GridTrack(string value)
|
||||
{
|
||||
value = value.Trim();
|
||||
|
||||
float f = 0;
|
||||
if (value.EndsWith("fr"))
|
||||
{
|
||||
if (float.TryParse(value.Substring(0, value.Length - 2), out f))
|
||||
{
|
||||
Fractional = f;
|
||||
Type = TrackSizeType.Fractional;
|
||||
}
|
||||
}
|
||||
|
||||
if (value.EndsWith("%"))
|
||||
{
|
||||
if (float.TryParse(value.Substring(0, value.Length - 1), out f))
|
||||
{
|
||||
Relative = f / 100f;
|
||||
Type = TrackSizeType.Relative;
|
||||
}
|
||||
}
|
||||
|
||||
if (float.TryParse(value, out f))
|
||||
{
|
||||
Absolute = f;
|
||||
Type = TrackSizeType.Absolute;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{Absolute},{Relative},{Fractional}]";
|
||||
|
||||
}
|
||||
|
||||
List<GridTrack> Rows = new();
|
||||
List<GridTrack> Columns = new();
|
||||
|
||||
public void CalculateTracks()
|
||||
{
|
||||
Rows.Clear();
|
||||
Columns.Clear();
|
||||
|
||||
if (Host.GridTemplateRows != null)
|
||||
{
|
||||
foreach (string s in Host.GridTemplateRows.Split(' '))
|
||||
{
|
||||
Rows.Add(new GridTrack(s));
|
||||
}
|
||||
}
|
||||
|
||||
if (Host.GridTemplateColumns != null)
|
||||
{
|
||||
foreach (string s in Host.GridTemplateColumns.Split(' '))
|
||||
{
|
||||
Columns.Add(new GridTrack(s));
|
||||
}
|
||||
}
|
||||
|
||||
if (Rows.Count == 0) Rows.Add(new GridTrack("100%"));
|
||||
if (Columns.Count == 0) Columns.Add(new GridTrack("100%"));
|
||||
|
||||
float x = 0;
|
||||
foreach (GridTrack track in Columns)
|
||||
{
|
||||
track.Start = x;
|
||||
track.Size = track.RealSize(Host.Real.Width);
|
||||
x += track.Size;
|
||||
track.End = x;
|
||||
}
|
||||
|
||||
float y = 0;
|
||||
foreach (GridTrack track in Rows)
|
||||
{
|
||||
track.Start = y;
|
||||
track.Size = track.RealSize(Host.Real.Height);
|
||||
y += track.Size;
|
||||
track.End = y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
if (Changed && Host.Children.Count > 0)
|
||||
{
|
||||
Host.InvokeOnLayoutUpdated();
|
||||
|
||||
CalculateTracks();
|
||||
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float w = 0;
|
||||
float h = 0;
|
||||
|
||||
int startCellX = 0;
|
||||
int startCellY = 0;
|
||||
if (c.GridStartCell != null)
|
||||
{
|
||||
startCellX = Math.Clamp(c.GridStartCell.Value.X, 0, Rows.Count);
|
||||
startCellY = Math.Clamp(c.GridStartCell.Value.Y, 0, Columns.Count);
|
||||
}
|
||||
|
||||
int endCellX = 0;
|
||||
int endCellY = 0;
|
||||
if (c.GridEndCell != null)
|
||||
{
|
||||
endCellX = Math.Clamp(c.GridEndCell.Value.X, 0, Rows.Count);
|
||||
endCellY = Math.Clamp(c.GridEndCell.Value.Y, 0, Columns.Count);
|
||||
}
|
||||
|
||||
CUIRect real = new CUIRect(
|
||||
Columns[startCellX].Start,
|
||||
Rows[startCellY].Start,
|
||||
Columns[endCellX].End - Columns[startCellX].Start,
|
||||
Rows[endCellY].End - Rows[startCellY].Start
|
||||
);
|
||||
|
||||
real = real.Shift(Host.Real.Position);
|
||||
|
||||
c.AmIOkWithThisSize(real.Size);
|
||||
|
||||
c.SetReal(real, "Grid Layout update");
|
||||
}
|
||||
}
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
public CUILayoutGrid() : base() { }
|
||||
public CUILayoutGrid(CUIComponent host) : base(host) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
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>
|
||||
/// Resizing components to it's Height and placing them sequentially
|
||||
/// </summary>
|
||||
public class CUILayoutHorizontalList : CUILayout
|
||||
{
|
||||
internal float TotalWidth;
|
||||
public CUIDirection Direction;
|
||||
public bool ResizeToHostHeight { get; set; } = true;
|
||||
|
||||
private class CUIComponentSize
|
||||
{
|
||||
public CUIComponent Component;
|
||||
public Vector2 Size;
|
||||
public CUIComponentSize(CUIComponent component, Vector2 size)
|
||||
{
|
||||
Component = component;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
private List<CUIComponentSize> Sizes = new List<CUIComponentSize>();
|
||||
private List<CUIComponentSize> Resizible = new List<CUIComponentSize>();
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
if (Changed)
|
||||
{
|
||||
Host.InvokeOnLayoutUpdated();
|
||||
|
||||
Sizes.Clear();
|
||||
Resizible.Clear();
|
||||
|
||||
TotalWidth = 0;
|
||||
|
||||
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
float h = 0;
|
||||
float w = 0;
|
||||
|
||||
if (ResizeToHostHeight)
|
||||
{
|
||||
h = Host.Real.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c.Relative.Height.HasValue) h = c.Relative.Height.Value * Host.Real.Height;
|
||||
if (c.CrossRelative.Height.HasValue) h = c.CrossRelative.Height.Value * Host.Real.Width;
|
||||
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
|
||||
|
||||
if (c.RelativeMin.Height.HasValue) h = Math.Max(h, c.RelativeMin.Height.Value * Host.Real.Height);
|
||||
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
|
||||
|
||||
if (!c.RelativeMin.Height.HasValue && !c.AbsoluteMin.Height.HasValue && c.ForcedMinSize.Y.HasValue)
|
||||
{
|
||||
h = Math.Max(h, c.ForcedMinSize.Y.Value);
|
||||
}
|
||||
|
||||
if (c.RelativeMax.Height.HasValue) h = Math.Min(h, c.RelativeMax.Height.Value * Host.Real.Height);
|
||||
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
|
||||
}
|
||||
|
||||
Vector2 s = new Vector2(w, h);
|
||||
|
||||
|
||||
if (!c.FillEmptySpace.X && !c.Ghost.X)
|
||||
{
|
||||
if (c.Relative.Width.HasValue)
|
||||
{
|
||||
w = c.Relative.Width.Value * Host.Real.Width;
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "Relative.Width", "w", w.ToString());
|
||||
}
|
||||
if (c.CrossRelative.Width.HasValue)
|
||||
{
|
||||
w = c.CrossRelative.Width.Value * Host.Real.Height;
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "CrossRelative.Width", "w", w.ToString());
|
||||
}
|
||||
if (c.Absolute.Width.HasValue)
|
||||
{
|
||||
w = c.Absolute.Width.Value;
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "Absolute.Width", "w", w.ToString());
|
||||
}
|
||||
|
||||
if (c.RelativeMin.Width.HasValue)
|
||||
{
|
||||
w = Math.Max(w, c.RelativeMin.Width.Value * Host.Real.Width);
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "RelativeMin.Width", "w", w.ToString());
|
||||
}
|
||||
if (c.AbsoluteMin.Width.HasValue)
|
||||
{
|
||||
w = Math.Max(w, c.AbsoluteMin.Width.Value);
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "AbsoluteMin.Width", "w", w.ToString());
|
||||
}
|
||||
if (!c.RelativeMin.Width.HasValue && !c.AbsoluteMin.Width.HasValue && c.ForcedMinSize.X.HasValue)
|
||||
{
|
||||
w = Math.Max(w, c.ForcedMinSize.X.Value);
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "ForcedMinSize.X", "w", w.ToString());
|
||||
}
|
||||
|
||||
if (c.RelativeMax.Width.HasValue)
|
||||
{
|
||||
w = Math.Min(w, c.RelativeMax.Width.Value * Host.Real.Width);
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "RelativeMax.Width", "w", w.ToString());
|
||||
}
|
||||
if (c.AbsoluteMax.Width.HasValue)
|
||||
{
|
||||
w = Math.Min(w, c.AbsoluteMax.Width.Value);
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "AbsoluteMax.Width", "w", w.ToString());
|
||||
}
|
||||
|
||||
s = new Vector2(w, h);
|
||||
Vector2 okSize = c.AmIOkWithThisSize(s);
|
||||
CUIDebug.Capture(Host, c, "HorizontalList.Update", "AmIOkWithThisSize", "s", okSize.ToString());
|
||||
|
||||
s = okSize;
|
||||
|
||||
if (!c.Fixed) s = new Vector2(s.X / c.Scale, s.Y);
|
||||
|
||||
TotalWidth += s.X;
|
||||
}
|
||||
|
||||
CUIComponentSize size = new CUIComponentSize(c, s);
|
||||
Sizes.Add(size);
|
||||
|
||||
if (c.FillEmptySpace.X) Resizible.Add(size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
float dif = Math.Max(0, Host.Real.Width - TotalWidth);
|
||||
|
||||
Resizible.ForEach(c =>
|
||||
{
|
||||
c.Size = c.Component.AmIOkWithThisSize(new Vector2((float)Math.Round(dif / Resizible.Count), c.Size.Y));
|
||||
//c.Size = new Vector2(dif / Resizible.Count, c.Size.Y);
|
||||
CUIDebug.Capture(Host, c.Component, "HorizontalList.Update", "Resizible.ForEach", "c.Size", c.Size.ToString());
|
||||
});
|
||||
|
||||
|
||||
CUI3DOffset offset = Host.ChildOffsetBounds.Check(Host.ChildrenOffset);
|
||||
|
||||
if (Direction == CUIDirection.Straight)
|
||||
{
|
||||
float x = 0;
|
||||
foreach (CUIComponentSize c in Sizes)
|
||||
{
|
||||
CUIRect real;
|
||||
if (Host.ChildrenBoundaries != null)
|
||||
{
|
||||
real = Host.ChildrenBoundaries(Host.Real).Check(x, 0, c.Size.X, c.Size.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
real = new CUIRect(x, 0, c.Size.X, c.Size.Y);
|
||||
}
|
||||
|
||||
real = offset.Transform(real);
|
||||
real = real.Shift(Host.Real.Position);
|
||||
|
||||
c.Component.SetReal(real, "HorizontalList layout update");
|
||||
|
||||
x += c.Size.X;
|
||||
}
|
||||
}
|
||||
|
||||
if (Direction == CUIDirection.Reverse)
|
||||
{
|
||||
float x = Host.Real.Width;
|
||||
foreach (CUIComponentSize c in Sizes)
|
||||
{
|
||||
x -= c.Size.X;
|
||||
|
||||
CUIRect real;
|
||||
if (Host.ChildrenBoundaries != null)
|
||||
{
|
||||
real = Host.ChildrenBoundaries(Host.Real).Check(x, 0, c.Size.X, c.Size.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
real = new CUIRect(x, 0, c.Size.X, c.Size.Y);
|
||||
}
|
||||
real = offset.Transform(real);
|
||||
real = real.Shift(Host.Real.Position);
|
||||
|
||||
c.Component.SetReal(real, "HorizontalList layout update");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
//TODO sync with vlist
|
||||
internal override void ResizeToContent()
|
||||
{
|
||||
if (AbsoluteChanged && Host.FitContent.X)
|
||||
{
|
||||
float tw = 0;
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
if (c.Ghost.X) continue;
|
||||
|
||||
float w = 0;
|
||||
if (!c.FillEmptySpace.X)
|
||||
{
|
||||
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
|
||||
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
|
||||
else if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
|
||||
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
|
||||
tw += w;
|
||||
}
|
||||
}
|
||||
|
||||
CUIDebug.Capture(null, Host, "HorizontalList ResizeToContent", "tw", "ForcedMinSize.X", tw.ToString());
|
||||
Host.SetForcedMinSize(Host.ForcedMinSize with { X = tw });
|
||||
}
|
||||
|
||||
if (AbsoluteChanged && Host.FitContent.Y)
|
||||
{
|
||||
float th = 0;
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
if (c.Ghost.Y) continue;
|
||||
|
||||
float h = 0;
|
||||
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
|
||||
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
|
||||
else if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
|
||||
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
|
||||
th = Math.Max(th, h);
|
||||
}
|
||||
|
||||
CUIDebug.Capture(null, Host, "HorizontalList ResizeToContent", "th", "ForcedMinSize.Y", th.ToString());
|
||||
Host.SetForcedMinSize(Host.ForcedMinSize with { Y = th });
|
||||
}
|
||||
|
||||
base.ResizeToContent();
|
||||
}
|
||||
public CUILayoutHorizontalList() : base() { }
|
||||
public CUILayoutHorizontalList(CUIDirection d, CUIComponent host = null) : base(host)
|
||||
{
|
||||
Direction = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
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>
|
||||
/// Default layout, simple descartes coordinate plane
|
||||
/// </summary>
|
||||
public class CUILayoutSimple : CUILayout
|
||||
{
|
||||
internal override void Update()
|
||||
{
|
||||
if (Changed && Host.Children.Count > 0)
|
||||
{
|
||||
Host.InvokeOnLayoutUpdated();
|
||||
|
||||
CUI3DOffset offset = Host.ChildOffsetBounds.Check(Host.ChildrenOffset);
|
||||
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
float x, y, w, h;
|
||||
|
||||
x = 0;
|
||||
if (c.Relative.Left.HasValue) x = c.Relative.Left.Value * Host.Real.Width;
|
||||
if (c.CrossRelative.Left.HasValue) x = c.CrossRelative.Left.Value * Host.Real.Height;
|
||||
if (c.Absolute.Left.HasValue) x = c.Absolute.Left.Value;
|
||||
|
||||
if (c.RelativeMin.Left.HasValue) x = Math.Max(x, c.RelativeMin.Left.Value * Host.Real.Width);
|
||||
if (c.AbsoluteMin.Left.HasValue) x = Math.Max(x, c.AbsoluteMin.Left.Value);
|
||||
|
||||
if (c.RelativeMax.Left.HasValue) x = Math.Min(x, c.RelativeMax.Left.Value * Host.Real.Width);
|
||||
if (c.AbsoluteMax.Left.HasValue) x = Math.Min(x, c.AbsoluteMax.Left.Value);
|
||||
|
||||
|
||||
y = 0;
|
||||
if (c.Relative.Top.HasValue) y = c.Relative.Top.Value * Host.Real.Height;
|
||||
if (c.CrossRelative.Top.HasValue) y = c.CrossRelative.Top.Value * Host.Real.Width;
|
||||
if (c.Absolute.Top.HasValue) y = c.Absolute.Top.Value;
|
||||
|
||||
if (c.RelativeMin.Top.HasValue) y = Math.Max(y, c.RelativeMin.Top.Value * Host.Real.Height);
|
||||
if (c.AbsoluteMin.Top.HasValue) y = Math.Max(y, c.AbsoluteMin.Top.Value);
|
||||
|
||||
if (c.RelativeMax.Top.HasValue) y = Math.Min(y, c.RelativeMax.Top.Value * Host.Real.Height);
|
||||
if (c.AbsoluteMax.Top.HasValue) y = Math.Min(y, c.AbsoluteMax.Top.Value);
|
||||
|
||||
|
||||
w = 0;
|
||||
if (c.Relative.Width.HasValue) w = c.Relative.Width.Value * Host.Real.Width;
|
||||
if (c.CrossRelative.Width.HasValue) w = c.CrossRelative.Width.Value * Host.Real.Height;
|
||||
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
|
||||
|
||||
if (c.RelativeMin.Width.HasValue) w = Math.Max(w, c.RelativeMin.Width.Value * Host.Real.Width);
|
||||
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
|
||||
|
||||
if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
|
||||
|
||||
if (c.RelativeMax.Width.HasValue) w = Math.Min(w, c.RelativeMax.Width.Value * Host.Real.Width);
|
||||
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
|
||||
|
||||
|
||||
h = 0;
|
||||
if (c.Relative.Height.HasValue) h = c.Relative.Height.Value * Host.Real.Height;
|
||||
if (c.CrossRelative.Height.HasValue) h = c.CrossRelative.Height.Value * Host.Real.Width;
|
||||
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
|
||||
|
||||
if (c.RelativeMin.Height.HasValue) h = Math.Max(h, c.RelativeMin.Height.Value * Host.Real.Height);
|
||||
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
|
||||
if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
|
||||
|
||||
|
||||
|
||||
if (c.RelativeMax.Height.HasValue) h = Math.Min(h, c.RelativeMax.Height.Value * Host.Real.Height);
|
||||
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
|
||||
|
||||
|
||||
(w, h) = c.AmIOkWithThisSize(new Vector2(w, h));
|
||||
(x, y) = CUIAnchor.GetChildPos(
|
||||
new CUIRect(Vector2.Zero, Host.Real.Size),
|
||||
c.ParentAnchor ?? c.Anchor,
|
||||
new Vector2(x, y),
|
||||
new Vector2(w, h),
|
||||
c.Anchor
|
||||
);
|
||||
CUIRect real;
|
||||
if (Host.ChildrenBoundaries != null)
|
||||
{
|
||||
real = Host.ChildrenBoundaries(Host.Real).Check(x, y, w, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
real = new CUIRect(x, y, w, h);
|
||||
}
|
||||
|
||||
if (!c.Fixed)
|
||||
{
|
||||
real = offset.Transform(real);
|
||||
}
|
||||
//TODO guh...
|
||||
real = real.Shift(Host.Real.Position);
|
||||
|
||||
|
||||
c.SetReal(real, "Simple Layout update");
|
||||
}
|
||||
}
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
internal override void ResizeToContent()
|
||||
{
|
||||
if (AbsoluteChanged && Host.FitContent.X)
|
||||
{
|
||||
float rightmostRight = 0;
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
if (c.Ghost.X) continue;
|
||||
|
||||
float x = 0;
|
||||
float w = 0;
|
||||
|
||||
if (c.Absolute.Left.HasValue) x = c.Absolute.Left.Value;
|
||||
if (c.AbsoluteMin.Left.HasValue) x = Math.Max(x, c.AbsoluteMin.Left.Value);
|
||||
if (c.AbsoluteMax.Left.HasValue) x = Math.Min(x, c.AbsoluteMax.Left.Value);
|
||||
|
||||
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
|
||||
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
|
||||
else if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
|
||||
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
|
||||
|
||||
rightmostRight = Math.Max(rightmostRight, x + w);
|
||||
}
|
||||
|
||||
Host.SetForcedMinSize(Host.ForcedMinSize with { X = rightmostRight });
|
||||
}
|
||||
|
||||
if (AbsoluteChanged && Host.FitContent.Y)
|
||||
{
|
||||
float bottommostBottom = 0;
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
if (c.Ghost.Y) continue;
|
||||
|
||||
float y = 0;
|
||||
float h = 0;
|
||||
|
||||
if (c.Absolute.Top.HasValue) y = c.Absolute.Top.Value;
|
||||
if (c.AbsoluteMin.Top.HasValue) y = Math.Max(y, c.AbsoluteMin.Top.Value);
|
||||
if (c.AbsoluteMax.Top.HasValue) y = Math.Min(y, c.AbsoluteMax.Top.Value);
|
||||
|
||||
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
|
||||
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
|
||||
else if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
|
||||
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
|
||||
|
||||
bottommostBottom = Math.Max(bottommostBottom, y + h);
|
||||
}
|
||||
|
||||
Host.SetForcedMinSize(Host.ForcedMinSize with { Y = bottommostBottom });
|
||||
}
|
||||
|
||||
base.ResizeToContent();
|
||||
}
|
||||
|
||||
public CUILayoutSimple() : base() { }
|
||||
public CUILayoutSimple(CUIComponent host) : base(host) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
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 CUILayoutVerticalList : CUILayout
|
||||
{
|
||||
internal float TotalHeight;
|
||||
public CUIDirection Direction;
|
||||
|
||||
public float Gap { get; set; }
|
||||
|
||||
public bool ResizeToHostWidth { get; set; } = true;
|
||||
|
||||
|
||||
private class CUIComponentSize
|
||||
{
|
||||
public CUIComponent Component;
|
||||
public Vector2 Size;
|
||||
public CUIComponentSize(CUIComponent component, Vector2 size)
|
||||
{
|
||||
Component = component;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
private List<CUIComponentSize> Sizes = new List<CUIComponentSize>();
|
||||
private List<CUIComponentSize> Resizible = new List<CUIComponentSize>();
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
|
||||
if (Changed)
|
||||
{
|
||||
Host.InvokeOnLayoutUpdated();
|
||||
|
||||
Sizes.Clear();
|
||||
Resizible.Clear();
|
||||
|
||||
TotalHeight = 0;
|
||||
|
||||
sw.Restart();
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
float h = 0;
|
||||
float w = 0;
|
||||
|
||||
if (ResizeToHostWidth)
|
||||
{
|
||||
w = Host.Real.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c.Relative.Width.HasValue) w = c.Relative.Width.Value * Host.Real.Width;
|
||||
if (c.CrossRelative.Width.HasValue) w = c.CrossRelative.Width.Value * Host.Real.Height;
|
||||
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
|
||||
|
||||
if (c.RelativeMin.Width.HasValue) w = Math.Max(w, c.RelativeMin.Width.Value * Host.Real.Width);
|
||||
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
|
||||
|
||||
if (!c.RelativeMin.Width.HasValue && !c.AbsoluteMin.Width.HasValue && c.ForcedMinSize.X.HasValue)
|
||||
{
|
||||
w = Math.Max(w, c.ForcedMinSize.X.Value);
|
||||
}
|
||||
|
||||
if (c.RelativeMax.Width.HasValue) w = Math.Min(w, c.RelativeMax.Width.Value * Host.Real.Width);
|
||||
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
|
||||
}
|
||||
|
||||
Vector2 s = new Vector2(w, h);
|
||||
|
||||
|
||||
if (!c.FillEmptySpace.Y && !c.Ghost.Y)
|
||||
{
|
||||
if (c.Relative.Height.HasValue)
|
||||
{
|
||||
h = c.Relative.Height.Value * Host.Real.Height;
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "Relative.Height", "h", h.ToString());
|
||||
}
|
||||
if (c.CrossRelative.Height.HasValue)
|
||||
{
|
||||
h = c.CrossRelative.Height.Value * Host.Real.Width;
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "CrossRelative.Height", "h", h.ToString());
|
||||
}
|
||||
if (c.Absolute.Height.HasValue)
|
||||
{
|
||||
h = c.Absolute.Height.Value;
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "Absolute.Height", "h", h.ToString());
|
||||
}
|
||||
|
||||
if (c.RelativeMin.Height.HasValue)
|
||||
{
|
||||
h = Math.Max(h, c.RelativeMin.Height.Value * Host.Real.Height);
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "RelativeMin.Height", "h", h.ToString());
|
||||
}
|
||||
if (c.AbsoluteMin.Height.HasValue)
|
||||
{
|
||||
h = Math.Max(h, c.AbsoluteMin.Height.Value);
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "AbsoluteMin.Height", "h", h.ToString());
|
||||
}
|
||||
if (!c.RelativeMin.Height.HasValue && !c.AbsoluteMin.Height.HasValue && c.ForcedMinSize.Y.HasValue)
|
||||
{
|
||||
h = Math.Max(h, c.ForcedMinSize.Y.Value);
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "ForcedMinSize.Y", "h", h.ToString());
|
||||
}
|
||||
|
||||
if (c.RelativeMax.Height.HasValue)
|
||||
{
|
||||
h = Math.Min(h, c.RelativeMax.Height.Value * Host.Real.Height);
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "RelativeMax.Height", "h", h.ToString());
|
||||
}
|
||||
if (c.AbsoluteMax.Height.HasValue)
|
||||
{
|
||||
h = Math.Min(h, c.AbsoluteMax.Height.Value);
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "AbsoluteMax.Height", "h", h.ToString());
|
||||
}
|
||||
|
||||
s = new Vector2(w, h);
|
||||
Vector2 okSize = c.AmIOkWithThisSize(s);
|
||||
CUIDebug.Capture(Host, c, "VerticalList.Update", "AmIOkWithThisSize", "s", okSize.ToString());
|
||||
|
||||
s = okSize;
|
||||
|
||||
if (!c.Fixed) s = new Vector2(s.X, s.Y / c.Scale);
|
||||
|
||||
TotalHeight += s.Y;
|
||||
}
|
||||
|
||||
CUIComponentSize size = new CUIComponentSize(c, s);
|
||||
Sizes.Add(size);
|
||||
|
||||
if (c.FillEmptySpace.Y) Resizible.Add(size);
|
||||
}
|
||||
|
||||
TotalHeight += Math.Max(0, Host.Children.Count - 1) * Gap;
|
||||
|
||||
sw.Stop();
|
||||
//CUI.Log($"{Host} vlist measuring {sw.ElapsedMilliseconds}");
|
||||
|
||||
float dif = Math.Max(0, Host.Real.Height - TotalHeight);
|
||||
|
||||
Resizible.ForEach(c =>
|
||||
{
|
||||
c.Size = c.Component.AmIOkWithThisSize(new Vector2(c.Size.X, (float)Math.Round(dif / Resizible.Count)));
|
||||
//c.Size = new Vector2(c.Size.X, dif / Resizible.Count);
|
||||
|
||||
CUIDebug.Capture(Host, c.Component, "VerticalList.Update", "Resizible.ForEach", "c.Size", c.Size.ToString());
|
||||
});
|
||||
|
||||
|
||||
CUI3DOffset offset = Host.ChildOffsetBounds.Check(Host.ChildrenOffset);
|
||||
|
||||
|
||||
|
||||
if (Direction == CUIDirection.Straight)
|
||||
{
|
||||
float y = 0;
|
||||
foreach (CUIComponentSize c in Sizes)
|
||||
{
|
||||
CUIRect real;
|
||||
|
||||
if (Host.ChildrenBoundaries != null) real = Host.ChildrenBoundaries(Host.Real).Check(0, y, c.Size.X, c.Size.Y);
|
||||
else real = new CUIRect(0, y, c.Size.X, c.Size.Y);
|
||||
|
||||
real = offset.Transform(real);
|
||||
real = real.Shift(Host.Real.Position);
|
||||
|
||||
c.Component.SetReal(real, "VerticalList.Update");
|
||||
|
||||
y += c.Size.Y + Gap;
|
||||
}
|
||||
}
|
||||
|
||||
if (Direction == CUIDirection.Reverse)
|
||||
{
|
||||
float y = Host.Real.Height;
|
||||
foreach (CUIComponentSize c in Sizes)
|
||||
{
|
||||
y -= c.Size.Y + Gap;
|
||||
CUIRect real;
|
||||
if (Host.ChildrenBoundaries != null) real = Host.ChildrenBoundaries(Host.Real).Check(0, y, c.Size.X, c.Size.Y);
|
||||
else real = new CUIRect(0, y, c.Size.X, c.Size.Y);
|
||||
|
||||
real = offset.Transform(real);
|
||||
real = real.Shift(Host.Real.Position);
|
||||
|
||||
c.Component.SetReal(real, "VerticalList.Update");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
internal override void ResizeToContent()
|
||||
{
|
||||
if (AbsoluteChanged && Host.FitContent.X)
|
||||
{
|
||||
float tw = 0;
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
if (c.Ghost.X) continue;
|
||||
|
||||
float w = 0;
|
||||
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
|
||||
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
|
||||
else if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
|
||||
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
|
||||
|
||||
tw = Math.Max(tw, w);
|
||||
}
|
||||
|
||||
CUIDebug.Capture(null, Host, "VerticalList.ResizeToContent", "tw", "ForcedMinSize.W", tw.ToString());
|
||||
Host.SetForcedMinSize(Host.ForcedMinSize with { X = tw });
|
||||
}
|
||||
|
||||
if (AbsoluteChanged && Host.FitContent.Y)
|
||||
{
|
||||
float th = 0;
|
||||
foreach (CUIComponent c in Host.Children)
|
||||
{
|
||||
if (c.Ghost.Y) continue;
|
||||
|
||||
float h = 0;
|
||||
if (!c.FillEmptySpace.Y)
|
||||
{
|
||||
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
|
||||
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
|
||||
else if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
|
||||
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
|
||||
th += h;
|
||||
}
|
||||
}
|
||||
|
||||
CUIDebug.Capture(null, Host, "VerticalList.ResizeToContent", "th", "ForcedMinSize.Y", th.ToString());
|
||||
Host.SetForcedMinSize(Host.ForcedMinSize with { Y = th });
|
||||
}
|
||||
|
||||
base.ResizeToContent();
|
||||
}
|
||||
|
||||
public CUILayoutVerticalList() : base() { }
|
||||
|
||||
public CUILayoutVerticalList(CUIDirection d, CUIComponent host = null) : base(host)
|
||||
{
|
||||
Direction = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all logic for resolving styles in the framework
|
||||
/// </summary>
|
||||
public static class CUIGlobalStyleResolver
|
||||
{
|
||||
|
||||
|
||||
public static void OnComponentStyleChanged(CUIComponent host)
|
||||
{
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(host.GetType());
|
||||
host.ResolvedStyle = CUIStyle.Merge(meta.ResolvedDefaultStyle, host.Style);
|
||||
ApplyStyleOn(host.ResolvedStyle, host);
|
||||
host.InvokeOnStyleApplied();
|
||||
}
|
||||
|
||||
public static void OnComponentStylePropChanged(CUIComponent host, string key)
|
||||
{
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(host.GetType());
|
||||
|
||||
if (meta.ResolvedDefaultStyle.Props.ContainsKey(key))
|
||||
{
|
||||
host.ResolvedStyle[key] = meta.ResolvedDefaultStyle[key];
|
||||
}
|
||||
|
||||
if (host.Style.Props.ContainsKey(key))
|
||||
{
|
||||
host.ResolvedStyle[key] = host.Style[key];
|
||||
}
|
||||
|
||||
ApplyStylePropOn(host.ResolvedStyle, key, host, meta);
|
||||
host.InvokeOnStyleApplied();
|
||||
}
|
||||
|
||||
public static void OnPaletteChange(CUIPalette palette)
|
||||
{
|
||||
foreach (Type CUIType in CUIReflection.CUITypes.Values)
|
||||
{
|
||||
foreach (CUIComponent c in CUIComponent.ComponentsByType.GetPage(CUIType))
|
||||
{
|
||||
ApplyStyleOn(c.ResolvedStyle, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnDefaultStyleChanged(Type CUIType)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Merge default styles
|
||||
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
|
||||
{
|
||||
node.Meta.ResolvedDefaultStyle = CUIStyle.Merge(
|
||||
node.Parent?.Meta.ResolvedDefaultStyle,
|
||||
node.Meta.DefaultStyle
|
||||
);
|
||||
});
|
||||
|
||||
// Apply default styles
|
||||
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
|
||||
{
|
||||
foreach (CUIComponent c in CUIComponent.ComponentsByType.GetPage(node.T))
|
||||
{
|
||||
OnComponentStyleChanged(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"OnDefaultStyleChanged| {e}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnDefaultStylePropChanged(Type CUIType, string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Merge default styles
|
||||
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
|
||||
{
|
||||
if (node.Parent != null)
|
||||
{
|
||||
if (node.Parent.Meta.ResolvedDefaultStyle.Props.ContainsKey(key))
|
||||
{
|
||||
node.Meta.ResolvedDefaultStyle[key] = node.Parent.Meta.ResolvedDefaultStyle[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (node.Meta.DefaultStyle.Props.ContainsKey(key))
|
||||
{
|
||||
node.Meta.ResolvedDefaultStyle[key] = node.Meta.DefaultStyle[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Apply default styles
|
||||
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
|
||||
{
|
||||
foreach (CUIComponent c in CUIComponent.ComponentsByType.GetPage(node.T))
|
||||
{
|
||||
OnComponentStylePropChanged(c, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static void ApplyStyleOn(CUIStyle style, CUIComponent target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
CUI.Warning($"Style target is null");
|
||||
return;
|
||||
}
|
||||
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(target.GetType());
|
||||
|
||||
foreach (string name in style.Props.Keys)
|
||||
{
|
||||
ApplyStylePropOn(style, name, target, meta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string CUIPalettePrefix = "CUIPalette.";
|
||||
/// <summary>
|
||||
/// Applies 1 prop with name on the target
|
||||
/// </summary>
|
||||
public static void ApplyStylePropOn(CUIStyle style, string name, CUIComponent target, CUITypeMetaData meta = null)
|
||||
{
|
||||
if (target.Unreal) return;
|
||||
|
||||
if (target == null) { CUI.Warning($"Style target is null"); return; }
|
||||
|
||||
meta ??= CUITypeMetaData.Get(target.GetType());
|
||||
|
||||
PropertyInfo pi = meta.Assignable.GetValueOrDefault(name);
|
||||
|
||||
if (pi == null)
|
||||
{
|
||||
if (CUIPalette.NotifyExcessivePropStyles) CUI.Warning($"Can't apply style: Couldn't find {name} prop in {target}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
string raw = style[name];
|
||||
|
||||
if (raw.StartsWith(CUIPalettePrefix))
|
||||
{
|
||||
PaletteExtractResult result = CUIPalette.Extract(raw.Substring(CUIPalettePrefix.Length), target.Palette);
|
||||
if (result.Ok)
|
||||
{
|
||||
raw = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CUIPalette.NotifiMissingPropStyles)
|
||||
{
|
||||
CUI.Warning($"Can't find {raw.Substring(CUIPalettePrefix.Length)} palette style for {target}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MethodInfo parse = CUIExtensions.Parse.GetValueOrDefault(pi.PropertyType);
|
||||
|
||||
parse ??= pi.PropertyType.GetMethod(
|
||||
"Parse",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
new Type[] { typeof(string) }
|
||||
);
|
||||
|
||||
if (parse == null)
|
||||
{
|
||||
CUI.Warning($"Can't parse style prop {name} for {target} because it's type {pi.PropertyType.Name} is missing Parse method");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pi.SetValue(target, parse.Invoke(null, new object[] { raw }));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Can't parse {raw} into {pi.PropertyType.Name} for {target}");
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
189
Quick Interactions/CSharp/Client/CrabUI/Style/CUIStyle.cs
Normal file
189
Quick Interactions/CSharp/Client/CrabUI/Style/CUIStyle.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
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>
|
||||
/// In Fact just an observable dict
|
||||
/// </summary>
|
||||
public partial class CUIStyle : IEnumerable<KeyValuePair<string, string>>, ICloneable
|
||||
{
|
||||
public static CUIStyle DefaultFor(Type T) => CUITypeMetaData.Get(T).DefaultStyle;
|
||||
public static CUIStyle DefaultFor<T>() where T : CUIComponent => CUITypeMetaData.Get(typeof(T)).DefaultStyle;
|
||||
|
||||
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
|
||||
=> Props.GetEnumerator();
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
=> Props.GetEnumerator();
|
||||
|
||||
public void Add(string key, string value)
|
||||
{
|
||||
Props[key] = value;
|
||||
OnPropChanged?.Invoke(key, value);
|
||||
}
|
||||
|
||||
public void Clear() => Props.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Prop name -> value
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Props = new();
|
||||
|
||||
public event Action<string, string> OnPropChanged;
|
||||
public event Action<CUIStyle> OnUse;
|
||||
|
||||
public virtual string this[string name]
|
||||
{
|
||||
get => Props.ContainsKey(name) ? Props[name] : "";
|
||||
set => Add(name, value);
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
CUIStyle style = new CUIStyle();
|
||||
style.Props = new Dictionary<string, string>(Props);
|
||||
return style;
|
||||
}
|
||||
|
||||
public static CUIStyle Merge(CUIStyle baseStyle, CUIStyle addedStyle)
|
||||
{
|
||||
CUIStyle result = new CUIStyle();
|
||||
|
||||
if (baseStyle != null)
|
||||
{
|
||||
foreach (string key in baseStyle.Props.Keys)
|
||||
{
|
||||
result[key] = baseStyle.Props[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (addedStyle != null)
|
||||
{
|
||||
foreach (string key in addedStyle.Props.Keys)
|
||||
{
|
||||
result[key] = addedStyle.Props[key];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Use(CUIStyle source)
|
||||
{
|
||||
Props = new Dictionary<string, string>(source.Props);
|
||||
OnUse?.Invoke(this);
|
||||
}
|
||||
|
||||
public void UseString(string raw)
|
||||
{
|
||||
Clear();
|
||||
|
||||
try
|
||||
{
|
||||
string content = raw.Split('{', '}')[1];
|
||||
if (content.Trim() == "") return;
|
||||
var pairs = content.Split(',').Select(s => s.Split(':').Select(sub => sub.Trim()).ToArray());
|
||||
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
Props[pair[0]] = pair[1];
|
||||
}
|
||||
}
|
||||
catch (Exception e) { CUI.Warning($"Style.UseString failed: {e.Message}"); }
|
||||
OnUse?.Invoke(this);
|
||||
}
|
||||
|
||||
public void UseXML(XElement element)
|
||||
{
|
||||
Clear();
|
||||
foreach (XElement prop in element.Elements())
|
||||
{
|
||||
Props[prop.Name.ToString()] = prop.Value;
|
||||
}
|
||||
OnUse?.Invoke(this);
|
||||
}
|
||||
|
||||
public static CUIStyle FromXML(XElement element)
|
||||
{
|
||||
CUIStyle style = new CUIStyle();
|
||||
style.UseXML(element);
|
||||
return style;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{ " + String.Join(", ", Props.Select(kvp => $"{kvp.Key} : {kvp.Value}")) + " }";
|
||||
}
|
||||
|
||||
public static CUIStyle Parse(string raw)
|
||||
{
|
||||
CUIStyle style = new CUIStyle();
|
||||
style.UseString(raw);
|
||||
return style;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is CUIStyle styleB)) return false;
|
||||
CUIStyle styleA = this;
|
||||
if (styleA is null && styleB is null) return true;
|
||||
if (styleA is null || styleB is null) return false;
|
||||
if (styleA.Props is null || styleB.Props is null) return false;
|
||||
if (styleA.Props.Count != styleB.Props.Count) return false;
|
||||
foreach (var (key, value) in styleA.Props)
|
||||
{
|
||||
if (!styleB.Props.ContainsKey(key)) return false;
|
||||
if (styleA[key] != styleB[key]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static CUIStyle operator +(CUIStyle styleA, CUIStyle styleB) => Merge(styleA, styleB);
|
||||
|
||||
public static bool operator ==(CUIStyle styleA, CUIStyle styleB)
|
||||
{
|
||||
if (styleA is null && styleB is null) return true;
|
||||
if (styleA is null || styleB is null) return false;
|
||||
if (styleA.Props is null || styleB.Props is null) return false;
|
||||
if (styleA.Props.Count != styleB.Props.Count) return false;
|
||||
foreach (var (key, value) in styleA.Props)
|
||||
{
|
||||
if (!styleB.Props.ContainsKey(key)) return false;
|
||||
if (styleA[key] != styleB[key]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator !=(CUIStyle styleA, CUIStyle styleB)
|
||||
{
|
||||
if (styleA is null && styleB is null) return false;
|
||||
if (styleA is null || styleB is null) return true;
|
||||
if (styleA.Props is null || styleB.Props is null) return true;
|
||||
if (styleA.Props.Count != styleB.Props.Count) return true;
|
||||
foreach (var (key, value) in styleA.Props)
|
||||
{
|
||||
if (!styleB.Props.ContainsKey(key)) return true;
|
||||
if (styleA[key] != styleB[key]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
namespace QICrabUI
|
||||
{
|
||||
public class CUIStyleLoader
|
||||
{
|
||||
internal static void InitStatic()
|
||||
{
|
||||
CUI.OnInit += () => LoadDefaultStyles();
|
||||
}
|
||||
|
||||
public static string DefaultStylesPath => Path.Combine(CUI.AssetsPath, "Default Styles.xml");
|
||||
|
||||
|
||||
public static void LoadDefaultStyles()
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
||||
if (CUI.AssetsPath == null) return;
|
||||
if (!File.Exists(DefaultStylesPath)) return;
|
||||
|
||||
|
||||
Dictionary<Type, CUIStyle> DefaultStyles = new();
|
||||
|
||||
XDocument xdoc = XDocument.Load(DefaultStylesPath);
|
||||
XElement root = xdoc.Element("DefaultStyles");
|
||||
foreach (XElement componentStyle in root.Elements())
|
||||
{
|
||||
Type componentType = CUIReflection.GetComponentTypeByName(componentStyle.Name.ToString());
|
||||
if (componentType == null)
|
||||
{
|
||||
CUI.Warning($"Couldn't find type for default style {componentStyle.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
DefaultStyles[componentType] = CUIStyle.FromXML(componentStyle);
|
||||
}
|
||||
sw.Stop();
|
||||
CUIDebug.Log($"Parsing default styles took {sw.ElapsedMilliseconds}ms");
|
||||
sw.Restart();
|
||||
|
||||
// It's heavy because CUITypeMetaData.Get creates defaults here
|
||||
foreach (Type T in DefaultStyles.Keys)
|
||||
{
|
||||
CUITypeMetaData.Get(T).DefaultStyle = DefaultStyles[T];
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
CUIDebug.Log($"Applying default styles took {sw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
17
Quick Interactions/CSharp/Client/CrabUI/Types/CUI enums.cs
Normal file
17
Quick Interactions/CSharp/Client/CrabUI/Types/CUI enums.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
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
|
||||
{
|
||||
public enum CUITextAlign { Start, Center, End, }
|
||||
public enum CUISide { Top, Right, Bottom, Left, }
|
||||
public enum CUIDirection { Straight, Reverse, }
|
||||
public enum CUIMouseEvent { Down, DClick }
|
||||
}
|
||||
115
Quick Interactions/CSharp/Client/CrabUI/Types/CUI3DOffset.cs
Normal file
115
Quick Interactions/CSharp/Client/CrabUI/Types/CUI3DOffset.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QICrabUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset of child components with X, Y, Z
|
||||
/// </summary>
|
||||
public struct CUI3DOffset
|
||||
{
|
||||
public float X;
|
||||
public float Y;
|
||||
//HACK this is mega cursed, it doens't want to be set to 1 automatically
|
||||
public float Z;
|
||||
|
||||
public Vector2 ToVector2 => new Vector2(X, Y);
|
||||
|
||||
//TODO unhardcode and move to CUIBoundaries
|
||||
public static float MinZ = 1f;
|
||||
|
||||
public CUI3DOffset Shift(Vector2 shift) => Shift(shift.X, shift.Y);
|
||||
public CUI3DOffset Shift(float x = 0, float y = 0)
|
||||
{
|
||||
return new CUI3DOffset(
|
||||
X + x * Z,
|
||||
Y + y * Z,
|
||||
Z
|
||||
);
|
||||
}
|
||||
|
||||
public CUI3DOffset Zoom(Vector2 staticPoint, float dZ) => Zoom(staticPoint.X, staticPoint.Y, dZ);
|
||||
public CUI3DOffset Zoom(float sx, float sy, float dZ)
|
||||
{
|
||||
float newZ = Math.Max(MinZ, Z + dZ);
|
||||
Vector2 s1 = new Vector2(sx * Z - X, sy * Z - Y);
|
||||
Vector2 s2 = new Vector2(sx * newZ - X, sy * newZ - Y);
|
||||
Vector2 d = s2 - s1;
|
||||
|
||||
return new CUI3DOffset(X + d.X, Y + d.Y, newZ);
|
||||
}
|
||||
|
||||
public Vector2 ToPlaneCoords(Vector2 v)
|
||||
{
|
||||
return new Vector2(v.X * Z - X, v.Y * Z - Y);
|
||||
}
|
||||
public Vector2 TransformPoint(Vector2 point)
|
||||
{
|
||||
return new Vector2((point.X + X) / Z, (point.Y + Y) / Z);
|
||||
}
|
||||
|
||||
public Vector2 TransformSize(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.X / Z, size.Y / Z);
|
||||
}
|
||||
|
||||
public CUIRect Transform(CUIRect rect)
|
||||
{
|
||||
return new CUIRect(
|
||||
(rect.Left + X) / Z,
|
||||
(rect.Top + Y) / Z,
|
||||
rect.Width / Z,
|
||||
rect.Height / Z
|
||||
);
|
||||
}
|
||||
|
||||
// XGLBRLGRLXBRSLRLGRKK!!!
|
||||
public CUI3DOffset()
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
Z = 1;
|
||||
}
|
||||
|
||||
public CUI3DOffset(float x, float y, float z)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{X},{Y},{Z}]";
|
||||
|
||||
public static CUI3DOffset Parse(string s)
|
||||
{
|
||||
string content = s.Substring(
|
||||
s.IndexOf('[') + 1,
|
||||
s.IndexOf(']') - s.IndexOf('[') - 1
|
||||
);
|
||||
|
||||
var components = content.Split(',').Select(a => a.Trim());
|
||||
|
||||
string sx = components.ElementAtOrDefault(0);
|
||||
string sy = components.ElementAtOrDefault(1);
|
||||
string sz = components.ElementAtOrDefault(2);
|
||||
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float z = 0;
|
||||
|
||||
float.TryParse(sx, out x);
|
||||
float.TryParse(sy, out y);
|
||||
float.TryParse(sz, out z);
|
||||
|
||||
return new CUI3DOffset(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Quick Interactions/CSharp/Client/CrabUI/Types/CUIAnchor.cs
Normal file
58
Quick Interactions/CSharp/Client/CrabUI/Types/CUIAnchor.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
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>
|
||||
/// CUIAnchor is just a Vector2
|
||||
/// This is a static class containing some helper methods
|
||||
/// </summary>
|
||||
public class CUIAnchor
|
||||
{
|
||||
public static Vector2 TopLeft = new Vector2(0.0f, 0.0f);
|
||||
public static Vector2 TopCenter = new Vector2(0.5f, 0.0f);
|
||||
public static Vector2 TopRight = new Vector2(1.0f, 0.0f);
|
||||
public static Vector2 CenterLeft = new Vector2(0.0f, 0.5f);
|
||||
public static Vector2 Center = new Vector2(0.5f, 0.5f);
|
||||
public static Vector2 CenterRight = new Vector2(1.0f, 0.5f);
|
||||
public static Vector2 BottomLeft = new Vector2(0.0f, 1.0f);
|
||||
public static Vector2 BottomCenter = new Vector2(0.5f, 1.0f);
|
||||
public static Vector2 BottomRight = new Vector2(1.0f, 1.0f);
|
||||
|
||||
public static Vector2 Direction(Vector2 anchor)
|
||||
{
|
||||
return (Center - anchor) * 2;
|
||||
}
|
||||
|
||||
public static Vector2 PosIn(CUIComponent host) => PosIn(host.Real, host.Anchor);
|
||||
public static Vector2 PosIn(CUIRect rect, Vector2 anchor)
|
||||
{
|
||||
return new Vector2(
|
||||
rect.Left + rect.Width * anchor.X,
|
||||
rect.Top + rect.Height * anchor.Y
|
||||
);
|
||||
}
|
||||
|
||||
public static Vector2 AnchorFromPos(CUIRect rect, Vector2 pos)
|
||||
{
|
||||
return (pos - rect.Position) / rect.Size;
|
||||
}
|
||||
|
||||
public static Vector2 GetChildPos(CUIRect parent, Vector2 anchor, Vector2 offset, Vector2 childSize)
|
||||
{
|
||||
return PosIn(parent, anchor) + offset - PosIn(new CUIRect(childSize), anchor);
|
||||
}
|
||||
|
||||
public static Vector2 GetChildPos(CUIRect parent, Vector2 parentAnchor, Vector2 offset, Vector2 childSize, Vector2 anchor)
|
||||
{
|
||||
return PosIn(parent, parentAnchor) + offset - PosIn(new CUIRect(childSize), anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBool2.cs
Normal file
49
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBool2.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
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>
|
||||
/// Vector2 but with bools
|
||||
/// </summary>
|
||||
public struct CUIBool2
|
||||
{
|
||||
public bool X;
|
||||
public bool Y;
|
||||
|
||||
public CUIBool2(bool x = false, bool y = false)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{X},{Y}]";
|
||||
public static CUIBool2 Parse(string s)
|
||||
{
|
||||
string content = s.Substring(
|
||||
s.IndexOf('[') + 1,
|
||||
s.IndexOf(']') - s.IndexOf('[') - 1
|
||||
);
|
||||
|
||||
var components = content.Split(',').Select(a => a.Trim());
|
||||
|
||||
string sx = components.ElementAtOrDefault(0);
|
||||
string sy = components.ElementAtOrDefault(1);
|
||||
|
||||
bool x = false;
|
||||
bool y = false;
|
||||
|
||||
if (sx != null && sx != "") x = bool.Parse(sx);
|
||||
if (sy != null && sy != "") y = bool.Parse(sy);
|
||||
|
||||
return new CUIBool2(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBorder.cs
Normal file
108
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBorder.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
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
|
||||
{
|
||||
|
||||
//TODO why is this mutable?
|
||||
public class CUIBorder : ICloneable
|
||||
{
|
||||
private Color color; public Color Color
|
||||
{
|
||||
get => color;
|
||||
set
|
||||
{
|
||||
color = value;
|
||||
UpdateVisible();
|
||||
}
|
||||
}
|
||||
private float thickness = 1f; public float Thickness
|
||||
{
|
||||
get => thickness;
|
||||
set
|
||||
{
|
||||
thickness = value;
|
||||
UpdateVisible();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Visible { get; set; }
|
||||
|
||||
|
||||
public void UpdateVisible()
|
||||
{
|
||||
Visible = Thickness != 0f && color != Color.Transparent;
|
||||
}
|
||||
|
||||
public CUIBorder() { }
|
||||
public CUIBorder(Color color, float thickness = 1f)
|
||||
{
|
||||
this.color = color;
|
||||
this.thickness = thickness;
|
||||
UpdateVisible();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is CUIBorder border)
|
||||
{
|
||||
if (Color == border.Color && Thickness == border.Thickness) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new CUIBorder(Color, Thickness);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => $"[{CUIExtensions.ColorToString(Color)},{Thickness}]";
|
||||
public static CUIBorder Parse(string raw)
|
||||
{
|
||||
CUIBorder border = new CUIBorder();
|
||||
try
|
||||
{
|
||||
string[] sub = raw.Split('[', ']');
|
||||
|
||||
if (sub.Length == 1)
|
||||
{
|
||||
border.Color = CUIExtensions.ParseColor(sub[0]);
|
||||
}
|
||||
|
||||
if (sub.Length > 1)
|
||||
{
|
||||
string content = raw.Split('[', ']').ElementAtOrDefault(1);
|
||||
|
||||
if (content.Trim() != "")
|
||||
{
|
||||
IEnumerable<string> values = content.Split(',');
|
||||
if (values.ElementAtOrDefault(0) != null)
|
||||
{
|
||||
border.Color = CUIExtensions.ParseColor(values.ElementAtOrDefault(0));
|
||||
}
|
||||
if (values.ElementAtOrDefault(1) != null)
|
||||
{
|
||||
float t = 1f;
|
||||
if (float.TryParse(values.ElementAtOrDefault(1), out t))
|
||||
{
|
||||
border.Thickness = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) { CUI.Warning($"Couldn't parse CUIBorder [{raw}]:\n{e.Message}"); }
|
||||
|
||||
return border;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
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
|
||||
{
|
||||
//TODO should be 2 different structs i think
|
||||
/// <summary>
|
||||
/// Defining Boundaries, not the same as rect
|
||||
/// containing min/max x, y, z
|
||||
/// </summary>
|
||||
public struct CUIBoundaries
|
||||
{
|
||||
public static Func<CUIRect, CUIBoundaries> Free = (Rect) => new CUIBoundaries(null, null, null, null);
|
||||
public static Func<CUIRect, CUIBoundaries> Box = (Rect) => new CUIBoundaries(0, Rect.Width, 0, Rect.Height);
|
||||
public static Func<CUIRect, CUIBoundaries> HorizontalTube = (Rect) => new CUIBoundaries(null, null, 0, Rect.Height);
|
||||
public static Func<CUIRect, CUIBoundaries> VerticalTube = (Rect) => new CUIBoundaries(0, Rect.Width, null, null);
|
||||
|
||||
|
||||
public float? MinX;
|
||||
public float? MaxX;
|
||||
public float? MinY;
|
||||
public float? MaxY;
|
||||
|
||||
//TODO minZ is hardcoded in CUI3DOffset, untangle this crap
|
||||
// unusable for now
|
||||
public float? MinZ;
|
||||
public float? MaxZ;
|
||||
|
||||
public CUIRect Check(float x, float y, float w, float h)
|
||||
{
|
||||
if (MaxX.HasValue && x + w > MaxX.Value) x = MaxX.Value - w;
|
||||
if (MaxY.HasValue && y + h > MaxY.Value) y = MaxY.Value - h;
|
||||
if (MinX.HasValue && x < MinX.Value) x = MinX.Value;
|
||||
if (MinY.HasValue && y < MinY.Value) y = MinY.Value;
|
||||
|
||||
return new CUIRect(x, y, w, h);
|
||||
}
|
||||
|
||||
|
||||
public CUI3DOffset Check(CUI3DOffset offset) => Check(offset.X, offset.Y, offset.Z);
|
||||
public CUI3DOffset Check(float x, float y, float z)
|
||||
{
|
||||
if (MaxX.HasValue && x > MaxX.Value) x = MaxX.Value;
|
||||
if (MaxY.HasValue && y > MaxY.Value) y = MaxY.Value;
|
||||
if (MaxZ.HasValue && z > MaxZ.Value) z = MaxZ.Value;
|
||||
|
||||
if (MinX.HasValue && x < MinX.Value) x = MinX.Value;
|
||||
if (MinY.HasValue && y < MinY.Value) y = MinY.Value;
|
||||
if (MinZ.HasValue && z < MinZ.Value) z = MinZ.Value;
|
||||
|
||||
return new CUI3DOffset(x, y, z);
|
||||
}
|
||||
|
||||
//HACK Why the fuck vars defined in this order?
|
||||
// probably to not confuse them with pos and size
|
||||
public CUIBoundaries(
|
||||
float? minX = null, float? maxX = null,
|
||||
float? minY = null, float? maxY = null,
|
||||
float? minZ = null, float? maxZ = null
|
||||
)
|
||||
{
|
||||
MinX = minX;
|
||||
MaxX = maxX;
|
||||
MinY = minY;
|
||||
MaxY = maxY;
|
||||
MinZ = minZ;
|
||||
MaxZ = maxZ;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{MinX},{MaxX},{MinY},{MaxY},{MinZ},{MaxZ}]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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>
|
||||
/// Vector2, but with float?
|
||||
/// </summary>
|
||||
public struct CUINullVector2
|
||||
{
|
||||
public float? X;
|
||||
public float? Y;
|
||||
|
||||
|
||||
public CUINullVector2()
|
||||
{
|
||||
X = null;
|
||||
Y = null;
|
||||
}
|
||||
public CUINullVector2(Vector2 v)
|
||||
{
|
||||
X = v.X;
|
||||
Y = v.Y;
|
||||
}
|
||||
public CUINullVector2(float? x, float? y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{X},{Y}]";
|
||||
|
||||
}
|
||||
}
|
||||
79
Quick Interactions/CSharp/Client/CrabUI/Types/CUIProp.cs
Normal file
79
Quick Interactions/CSharp/Client/CrabUI/Types/CUIProp.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper object for prop value, setters, side effects, metadata etc.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> Type of the prop </typeparam>
|
||||
public class CUIProp<T> : ICUIProp
|
||||
{
|
||||
public void SetHost(CUIComponent host) => Host = host;
|
||||
public void SetName(string name) => Name = name;
|
||||
|
||||
public CUIComponent Host;
|
||||
public string Name = "Unknown";
|
||||
public Action<T, CUIComponent> OnSet;
|
||||
public Func<T, CUIComponent, T> Validate;
|
||||
public bool LayoutProp;
|
||||
public bool DecorProp;
|
||||
public bool AbsoluteProp;
|
||||
public bool ChildProp;
|
||||
public bool ShowInDebug = true;
|
||||
|
||||
public T Value;
|
||||
public void SetValue(T value, [CallerMemberName] string memberName = "")
|
||||
{
|
||||
if (Host == null)
|
||||
{
|
||||
CUI.Log($"{Name} CUIProp doens't have a Host! Type: {typeof(T)} Value: {value} memberName: {memberName}", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Validate == null)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = Validate.Invoke(value, Host);
|
||||
}
|
||||
|
||||
OnSet?.Invoke(value, Host);
|
||||
|
||||
if (ShowInDebug)
|
||||
{
|
||||
CUIDebug.Capture(null, Host, "SetValue", memberName, Name, Value.ToString());
|
||||
}
|
||||
|
||||
|
||||
if (LayoutProp) Host.OnPropChanged();
|
||||
if (DecorProp) Host.OnDecorPropChanged();
|
||||
if (AbsoluteProp) Host.OnAbsolutePropChanged();
|
||||
if (ChildProp) Host.OnChildrenPropChanged();
|
||||
}
|
||||
|
||||
public CUIProp() { }
|
||||
|
||||
public CUIProp(CUIComponent host, string name)
|
||||
{
|
||||
Name = name;
|
||||
Host = host;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
174
Quick Interactions/CSharp/Client/CrabUI/Types/CUIRect.cs
Normal file
174
Quick Interactions/CSharp/Client/CrabUI/Types/CUIRect.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
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>
|
||||
/// Rectangle with float
|
||||
/// </summary>
|
||||
public struct CUIRect
|
||||
{
|
||||
/// <summary>
|
||||
/// IDK where to put it
|
||||
/// </summary>
|
||||
public static Rectangle CreateRect(float x, float y, float w, float h)
|
||||
{
|
||||
return new Rectangle((int)Math.Round(x), (int)Math.Round(y), (int)Math.Round(w), (int)Math.Round(h));
|
||||
}
|
||||
|
||||
|
||||
public float Left;
|
||||
public float Top;
|
||||
public float Width;
|
||||
public float Height;
|
||||
|
||||
public float Right => Left + Width;
|
||||
public float Bottom => Top + Height;
|
||||
|
||||
public Vector2 Size => new Vector2(Width, Height);
|
||||
public Vector2 Position => new Vector2(Left, Top);
|
||||
public Vector2 Center => new Vector2(Left + Width / 2, Top + Height / 2);
|
||||
public Rectangle Box => new Rectangle((int)Left, (int)Top, (int)Width, (int)Height);
|
||||
|
||||
public Vector2 LeftTop => new Vector2(Left, Top);
|
||||
public Vector2 LeftCenter => new Vector2(Left, Top + Height / 2);
|
||||
public Vector2 LeftBottom => new Vector2(Left, Top + Height);
|
||||
public Vector2 CenterTop => new Vector2(Left + Width / 2, Top);
|
||||
public Vector2 CenterCenter => new Vector2(Left + Width / 2, Top + Height / 2);
|
||||
public Vector2 CenterBottom => new Vector2(Left + Width / 2, Top + Height);
|
||||
public Vector2 RightTop => new Vector2(Left + Width, Top);
|
||||
public Vector2 RightCenter => new Vector2(Left + Width, Top + Height / 2);
|
||||
public Vector2 RightBottom => new Vector2(Left + Width, Top + Height);
|
||||
|
||||
|
||||
|
||||
public CUIRect Shift(Vector2 shift)
|
||||
{
|
||||
return new CUIRect(Left + shift.X, Top + shift.Y, Width, Height);
|
||||
}
|
||||
|
||||
public bool Contains(float x, float y)
|
||||
{
|
||||
return Left < x && x < Right && Top < y && y < Bottom;
|
||||
}
|
||||
public bool Contains(Vector2 pos)
|
||||
{
|
||||
return Left < pos.X && pos.X < Right && Top < pos.Y && pos.Y < Bottom;
|
||||
}
|
||||
public bool Intersect(CUIRect r)
|
||||
{
|
||||
return r.Right >= Left && r.Left <= Right && r.Bottom >= Top && r.Top <= Bottom;
|
||||
}
|
||||
|
||||
public Rectangle Zoom(float Z)
|
||||
{
|
||||
Vector2 ScreenCenter = CUI.GameScreenSize / 2.0f;
|
||||
Vector2 PosDif = Position - ScreenCenter;
|
||||
Vector2 newPos = PosDif * Z + ScreenCenter;
|
||||
|
||||
return new Rectangle(
|
||||
(int)newPos.X, (int)newPos.Y,
|
||||
(int)(Width / Z), (int)(Height / Z)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public CUIRect(Vector2 size) : this(0, 0, size.X, size.Y) { }
|
||||
public CUIRect(Vector2 position, Vector2 size) : this(position.X, position.Y, size.X, size.Y) { }
|
||||
public CUIRect(float x, float y, float w, float h)
|
||||
{
|
||||
Left = x;
|
||||
Top = y;
|
||||
Width = Math.Max(0f, w);
|
||||
Height = Math.Max(0f, h);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override string ToString() => $"[{Left},{Top},{Width},{Height}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rectangle with float?
|
||||
/// </summary>
|
||||
public struct CUINullRect
|
||||
{
|
||||
// Guh...
|
||||
public static CUINullRect Parse(string s)
|
||||
{
|
||||
string content = s.Substring(
|
||||
s.IndexOf('[') + 1,
|
||||
s.IndexOf(']') - s.IndexOf('[') - 1
|
||||
);
|
||||
|
||||
var components = content.Split(',').Select(a => a.Trim());
|
||||
|
||||
string sx = components.ElementAtOrDefault(0);
|
||||
string sy = components.ElementAtOrDefault(1);
|
||||
string sw = components.ElementAtOrDefault(2);
|
||||
string sh = components.ElementAtOrDefault(3);
|
||||
|
||||
float? x = null;
|
||||
float? y = null;
|
||||
float? w = null;
|
||||
float? h = null;
|
||||
|
||||
if (sx == null || sx == "") x = null;
|
||||
else x = float.Parse(sx);
|
||||
|
||||
if (sy == null || sy == "") y = null;
|
||||
else y = float.Parse(sy);
|
||||
|
||||
if (sw == null || sw == "") w = null;
|
||||
else w = float.Parse(sw);
|
||||
|
||||
if (sh == null || sh == "") h = null;
|
||||
else h = float.Parse(sh);
|
||||
|
||||
return new CUINullRect(x, y, w, h);
|
||||
}
|
||||
|
||||
public float? Left;
|
||||
public float? Top;
|
||||
public float? Width;
|
||||
public float? Height;
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get => new Vector2(Width ?? 0, Height ?? 0);
|
||||
set { Width = value.X; Height = value.Y; }
|
||||
}
|
||||
public Vector2 Position
|
||||
{
|
||||
get => new Vector2(Left ?? 0, Top ?? 0);
|
||||
set { Left = value.X; Top = value.Y; }
|
||||
}
|
||||
|
||||
public Vector2 Center => new Vector2(
|
||||
(Left ?? 0) + (Width ?? 0) / 2,
|
||||
(Top ?? 0) + (Height ?? 0) / 2
|
||||
);
|
||||
|
||||
public CUINullRect(Vector2 position, Vector2 size) : this(position.X, position.Y, size.X, size.Y) { }
|
||||
|
||||
public CUINullRect(float? x = null, float? y = null, float? w = null, float? h = null)
|
||||
{
|
||||
Left = x;
|
||||
Top = y;
|
||||
Width = w;
|
||||
Height = h;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{Left},{Top},{Width},{Height}]";
|
||||
|
||||
}
|
||||
}
|
||||
317
Quick Interactions/CSharp/Client/CrabUI/Types/CUISprite.cs
Normal file
317
Quick Interactions/CSharp/Client/CrabUI/Types/CUISprite.cs
Normal file
@@ -0,0 +1,317 @@
|
||||
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.IO;
|
||||
namespace QICrabUI
|
||||
{
|
||||
public enum CUISpriteDrawMode
|
||||
{
|
||||
Resize,
|
||||
Wrap,
|
||||
Static,
|
||||
StaticDeep,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper Containing link to texture and drawing settings,
|
||||
/// like SourceRedt, DrawMode, Effects, Rotation...
|
||||
/// Multiple sprites can use the same texture
|
||||
/// </summary>
|
||||
public class CUISprite : ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// 1x1 white texture
|
||||
/// </summary>
|
||||
public static Texture2D BackupTexture => GUI.WhiteTexture;
|
||||
/// <summary>
|
||||
/// new Sprite that uses 1x1 default texture
|
||||
/// </summary>
|
||||
public static CUISprite Default => new CUISprite();
|
||||
|
||||
/// <summary>
|
||||
/// Set when you load it from some path
|
||||
/// </summary>
|
||||
public string Path = "";
|
||||
/// <summary>
|
||||
/// None, FlipHorizontally, FlipVertically
|
||||
/// </summary>
|
||||
public SpriteEffects Effects;
|
||||
/// <summary>
|
||||
/// Resize - will resize the sprite to component
|
||||
/// Wrap - will loop the texture
|
||||
/// Static - sprite ignores component position
|
||||
/// </summary>
|
||||
public CUISpriteDrawMode DrawMode;
|
||||
/// <summary>
|
||||
/// Part of the texture that is drawn
|
||||
/// Won't work in Wrap mode becase it will loop the whole texture
|
||||
/// </summary>
|
||||
public Rectangle SourceRect;
|
||||
private Texture2D texture = BackupTexture;
|
||||
/// <summary>
|
||||
/// The link to the texture
|
||||
/// Multiple sprites can use the same texture
|
||||
/// </summary>
|
||||
public Texture2D Texture
|
||||
{
|
||||
get => texture;
|
||||
set
|
||||
{
|
||||
texture = value;
|
||||
SourceRect = new Rectangle(0, 0, texture.Width, texture.Height);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// In radians
|
||||
/// </summary>
|
||||
public float Rotation;
|
||||
/// <summary>
|
||||
/// In degree
|
||||
/// </summary>
|
||||
public float RotationAngle
|
||||
{
|
||||
get => (float)(Rotation * 180 / Math.PI);
|
||||
set => Rotation = (float)(value * Math.PI / 180);
|
||||
}
|
||||
/// <summary>
|
||||
/// Origin of rotation in pixels
|
||||
/// </summary>
|
||||
public Vector2 Origin;
|
||||
/// <summary>
|
||||
/// Origin of rotation in [0..1] of texture size
|
||||
/// </summary>
|
||||
public Vector2 RelativeOrigin
|
||||
{
|
||||
set
|
||||
{
|
||||
if (Texture == null) return;
|
||||
Origin = new Vector2(value.X * Texture.Width, value.Y * Texture.Height);
|
||||
}
|
||||
}
|
||||
private Vector2 offset;
|
||||
/// <summary>
|
||||
/// Draw offset from CUIComponent Position
|
||||
/// For your convenience also sets origin
|
||||
/// </summary>
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => offset;
|
||||
set
|
||||
{
|
||||
offset = value;
|
||||
RelativeOrigin = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is not CUISprite b) return false;
|
||||
|
||||
return Texture == b.Texture &&
|
||||
SourceRect == b.SourceRect &&
|
||||
DrawMode == b.DrawMode &&
|
||||
Effects == b.Effects &&
|
||||
Rotation == b.Rotation &&
|
||||
Origin == b.Origin &&
|
||||
Offset == b.Offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a CUISprite from vanilla Sprite
|
||||
/// </summary>
|
||||
public static CUISprite FromVanilla(Sprite sprite)
|
||||
{
|
||||
if (sprite == null) return Default;
|
||||
|
||||
return new CUISprite(sprite.Texture, sprite.SourceRect)
|
||||
{
|
||||
Path = sprite.FullPath,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses vanilla GUI sprite from GUIStyle.ComponentStyles with this name
|
||||
/// </summary>
|
||||
public static CUISprite FromName(string name) => FromId(new Identifier(name));
|
||||
public static CUISprite FromId(Identifier id)
|
||||
{
|
||||
GUIComponentStyle? style = GUIStyle.ComponentStyles[id];
|
||||
if (style == null) return Default;
|
||||
|
||||
return FromComponentStyle(style);
|
||||
}
|
||||
|
||||
public static CUISprite FromComponentStyle(GUIComponentStyle style, GUIComponent.ComponentState state = GUIComponent.ComponentState.None)
|
||||
{
|
||||
return FromVanilla(style.Sprites[state].FirstOrDefault()?.Sprite);
|
||||
}
|
||||
|
||||
//TODO add using construction
|
||||
/// <summary>
|
||||
/// When you load sprite from file, relative paths are considered relative to barotrauma folder
|
||||
/// if BaseFolder != null sprite will check files in BaseFolder first
|
||||
/// Don't forget to set it back to null
|
||||
/// </summary>
|
||||
public static string BaseFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default 1x1 white sprite
|
||||
/// </summary>
|
||||
public CUISprite()
|
||||
{
|
||||
Texture = BackupTexture;
|
||||
SourceRect = new Rectangle(0, 0, Texture.Width, Texture.Height);
|
||||
}
|
||||
public CUISprite(string path, Rectangle? sourceRect = null, string baseFolder = null)
|
||||
{
|
||||
baseFolder ??= BaseFolder;
|
||||
string realpath = path;
|
||||
|
||||
if (!System.IO.Path.IsPathRooted(path) && baseFolder != null)
|
||||
{
|
||||
string localPath = System.IO.Path.Combine(baseFolder, path);
|
||||
if (File.Exists(localPath)) realpath = localPath;
|
||||
}
|
||||
|
||||
Path = path;
|
||||
Texture = CUI.TextureManager.GetTexture(realpath);
|
||||
if (sourceRect.HasValue) SourceRect = sourceRect.Value;
|
||||
}
|
||||
public CUISprite(Texture2D texture, Rectangle? sourceRect = null)
|
||||
{
|
||||
Texture = texture ?? BackupTexture;
|
||||
if (sourceRect.HasValue) SourceRect = sourceRect.Value;
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
CUISprite sprite = new CUISprite(Texture, SourceRect)
|
||||
{
|
||||
Path = this.Path,
|
||||
Rotation = this.Rotation,
|
||||
Offset = this.Offset,
|
||||
Origin = this.Origin,
|
||||
Effects = this.Effects,
|
||||
DrawMode = this.DrawMode,
|
||||
};
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string mode = DrawMode != CUISpriteDrawMode.Resize ? $", Mode: {DrawMode}" : "";
|
||||
string rect = SourceRect != Texture.Bounds ? $", SourceRect: {CUIExtensions.RectangleToString(SourceRect)}" : "";
|
||||
string effect = Effects != SpriteEffects.None ? $", Effects: {CUIExtensions.SpriteEffectsToString(Effects)}" : "";
|
||||
|
||||
string rotation = Rotation != 0.0f ? $", Rotation: {Rotation}" : "";
|
||||
string offset = Offset != Vector2.Zero ? $", Offset: {CUIExtensions.Vector2ToString(Offset)}" : "";
|
||||
string origin = Origin != Vector2.Zero ? $", Origin: {CUIExtensions.Vector2ToString(Origin)}" : "";
|
||||
|
||||
return $"{{ Path: {Path}{mode}{rect}{effect}{rotation}{offset}{origin} }}";
|
||||
}
|
||||
|
||||
//BUG it can't use absolute paths because of the c://
|
||||
public static CUISprite Parse(string raw)
|
||||
{
|
||||
Dictionary<string, string> props = CUIExtensions.ParseKVPairs(raw);
|
||||
|
||||
if (!props.ContainsKey("path")) return new CUISprite();
|
||||
|
||||
CUISprite sprite = CUI.TextureManager.GetSprite(props["path"]);
|
||||
if (props.ContainsKey("mode"))
|
||||
{
|
||||
sprite.DrawMode = Enum.Parse<CUISpriteDrawMode>(props["mode"]);
|
||||
}
|
||||
if (props.ContainsKey("sourcerect"))
|
||||
{
|
||||
sprite.SourceRect = CUIExtensions.ParseRectangle(props["sourcerect"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.SourceRect = new Rectangle(0, 0, sprite.Texture.Width, sprite.Texture.Height);
|
||||
}
|
||||
if (props.ContainsKey("effects"))
|
||||
{
|
||||
sprite.Effects = CUIExtensions.ParseSpriteEffects(props["effects"]);
|
||||
}
|
||||
|
||||
if (props.ContainsKey("rotation"))
|
||||
{
|
||||
float r;
|
||||
float.TryParse(props["rotation"], out r);
|
||||
sprite.Rotation = r;
|
||||
}
|
||||
|
||||
if (props.ContainsKey("offset"))
|
||||
{
|
||||
sprite.Offset = CUIExtensions.ParseVector2(props["offset"]);
|
||||
}
|
||||
|
||||
if (props.ContainsKey("origin"))
|
||||
{
|
||||
sprite.Origin = CUIExtensions.ParseVector2(props["origin"]);
|
||||
}
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
//TODO find less hacky solution
|
||||
public static CUISprite ParseWithContext(string raw, string baseFolder = null)
|
||||
{
|
||||
Dictionary<string, string> props = CUIExtensions.ParseKVPairs(raw);
|
||||
|
||||
if (!props.ContainsKey("path")) return new CUISprite();
|
||||
|
||||
if (!System.IO.Path.IsPathRooted(props["path"]) && baseFolder != null)
|
||||
{
|
||||
string localPath = System.IO.Path.Combine(baseFolder, props["path"]);
|
||||
|
||||
if (File.Exists(localPath)) props["path"] = localPath;
|
||||
}
|
||||
|
||||
CUISprite sprite = CUI.TextureManager.GetSprite(props["path"]);
|
||||
if (props.ContainsKey("mode"))
|
||||
{
|
||||
sprite.DrawMode = Enum.Parse<CUISpriteDrawMode>(props["mode"]);
|
||||
}
|
||||
if (props.ContainsKey("sourcerect"))
|
||||
{
|
||||
sprite.SourceRect = CUIExtensions.ParseRectangle(props["sourcerect"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.SourceRect = new Rectangle(0, 0, sprite.Texture.Width, sprite.Texture.Height);
|
||||
}
|
||||
if (props.ContainsKey("effects"))
|
||||
{
|
||||
sprite.Effects = CUIExtensions.ParseSpriteEffects(props["effects"]);
|
||||
}
|
||||
|
||||
if (props.ContainsKey("rotation"))
|
||||
{
|
||||
float r;
|
||||
float.TryParse(props["rotation"], out r);
|
||||
sprite.Rotation = r;
|
||||
}
|
||||
|
||||
if (props.ContainsKey("offset"))
|
||||
{
|
||||
sprite.Offset = CUIExtensions.ParseVector2(props["offset"]);
|
||||
}
|
||||
|
||||
if (props.ContainsKey("origin"))
|
||||
{
|
||||
sprite.Origin = CUIExtensions.ParseVector2(props["origin"]);
|
||||
}
|
||||
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Props implementing this will be bound to their host
|
||||
/// with reflection after creation of the host
|
||||
/// </summary>
|
||||
public interface ICUIVitalizable
|
||||
{
|
||||
public void SetHost(CUIComponent host);
|
||||
}
|
||||
|
||||
public interface ICUIProp : ICUIVitalizable
|
||||
{
|
||||
public void SetName(string name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// For stuff that should be in some way refreshed
|
||||
/// This method appears way too many times
|
||||
/// There are also secret public void CascadeRefresh() method in CUIComponent.Events that refreshes all childs recursivelly
|
||||
/// </summary>
|
||||
public interface IRefreshable
|
||||
{
|
||||
public void Refresh();
|
||||
}
|
||||
}
|
||||
33
Quick Interactions/CSharp/Client/CrabUI/Types/Indexer.cs
Normal file
33
Quick Interactions/CSharp/Client/CrabUI/Types/Indexer.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
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
|
||||
{
|
||||
//xd
|
||||
public class Indexer<TKey, TValue>
|
||||
{
|
||||
public Func<TKey, TValue> Get;
|
||||
public Action<TKey, TValue> Set;
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get => Get(key);
|
||||
set => Set(key, value);
|
||||
}
|
||||
|
||||
public Indexer(Func<TKey, TValue> get, Action<TKey, TValue> set) => (Get, Set) = (get, set);
|
||||
}
|
||||
}
|
||||
90
Quick Interactions/CSharp/Client/CrabUI/Types/Ranges.cs
Normal file
90
Quick Interactions/CSharp/Client/CrabUI/Types/Ranges.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
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>
|
||||
/// Same as Range but with normal ints
|
||||
/// </summary>
|
||||
public struct IntRange
|
||||
{
|
||||
public static IntRange Zero = new IntRange(0, 0);
|
||||
public int Start;
|
||||
public int End;
|
||||
public bool IsZero => Start == 0 && End == 0;
|
||||
public bool IsEmpty => End - Start <= 0;
|
||||
public IntRange(int start, int end)
|
||||
{
|
||||
if (end >= start) (Start, End) = (start, end);
|
||||
else (End, Start) = (start, end);
|
||||
}
|
||||
public static bool operator ==(IntRange a, IntRange b) => a.Start == b.Start && a.End == b.End;
|
||||
public static bool operator !=(IntRange a, IntRange b) => a.Start != b.Start || a.End != b.End;
|
||||
|
||||
public override string ToString() => $"[{Start},{End}]";
|
||||
public static IntRange Parse(string raw)
|
||||
{
|
||||
if (raw == null || raw == "") return new IntRange(0, 0);
|
||||
|
||||
string content = raw.Split('[', ']')[1];
|
||||
|
||||
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
|
||||
|
||||
int start;
|
||||
int end;
|
||||
|
||||
int.TryParse(coords.ElementAtOrDefault(0), out start);
|
||||
int.TryParse(coords.ElementAtOrDefault(1), out end);
|
||||
|
||||
return new IntRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as Range but with normal floats
|
||||
/// </summary>
|
||||
public struct FloatRange
|
||||
{
|
||||
public static FloatRange Zero = new FloatRange(0, 0);
|
||||
public float Start;
|
||||
public float End;
|
||||
public bool IsZero => Start == 0 && End == 0;
|
||||
public bool IsEmpty => End - Start <= 0;
|
||||
|
||||
public float PosOf(float lambda) => (End - Start) * lambda;
|
||||
public float LambdaOf(float pos) => (pos - Start) / (End - Start);
|
||||
public FloatRange(float start, float end)
|
||||
{
|
||||
if (end >= start) (Start, End) = (start, end);
|
||||
else (End, Start) = (start, end);
|
||||
}
|
||||
public static bool operator ==(FloatRange a, FloatRange b) => a.Start == b.Start && a.End == b.End;
|
||||
public static bool operator !=(FloatRange a, FloatRange b) => a.Start != b.Start || a.End != b.End;
|
||||
|
||||
public override string ToString() => $"[{Start},{End}]";
|
||||
|
||||
public static FloatRange Parse(string raw)
|
||||
{
|
||||
if (raw == null || raw == "") return new FloatRange(0, 0);
|
||||
|
||||
string content = raw.Split('[', ']')[1];
|
||||
|
||||
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
|
||||
|
||||
float start;
|
||||
float end;
|
||||
|
||||
float.TryParse(coords.ElementAtOrDefault(0), out start);
|
||||
float.TryParse(coords.ElementAtOrDefault(1), out end);
|
||||
|
||||
return new FloatRange(start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Quick Interactions/CSharp/Client/CrabUI/Types/WeakCatalog.cs
Normal file
56
Quick Interactions/CSharp/Client/CrabUI/Types/WeakCatalog.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
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.Runtime.CompilerServices;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
|
||||
public class WeakCatalog<TKey, TValue> where TValue : class
|
||||
{
|
||||
public Dictionary<TKey, List<WeakReference<TValue>>> Pages = new();
|
||||
|
||||
public Dictionary<TKey, List<WeakReference<TValue>>>.KeyCollection Keys => Pages.Keys;
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
if (!Pages.ContainsKey(key)) Pages[key] = new();
|
||||
Pages[key].Add(new WeakReference<TValue>(value));
|
||||
}
|
||||
|
||||
public void Clear() => Pages.Clear();
|
||||
|
||||
public void RemoveEmptyLinks(TKey key)
|
||||
{
|
||||
if (!Pages.ContainsKey(key))
|
||||
{
|
||||
Pages[key] = new();
|
||||
return;
|
||||
}
|
||||
|
||||
Pages[key] = Pages[key].Where(wr =>
|
||||
{
|
||||
TValue value = null;
|
||||
wr.TryGetTarget(out value);
|
||||
return value is not null;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<TValue> GetPage(TKey key)
|
||||
{
|
||||
RemoveEmptyLinks(key);
|
||||
return Pages[key].Select(wr =>
|
||||
{
|
||||
TValue value = null;
|
||||
wr.TryGetTarget(out value);
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user