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 { /// /// Base class for all components /// 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> ComponentsById = new(); public static WeakCatalog ComponentsByType = new(); /// /// This is used to trick vanilla GUI into believing that /// mouse is hovering some component and block clicks /// public static GUIButton dummyComponent = new GUIButton(new RectTransform(new Point(0, 0))) { Text = "DUMMY", }; /// /// designed to be versatile, in fact never used /// public static void RunRecursiveOn(CUIComponent component, Action action) { action(component); foreach (CUIComponent child in component.Children) { RunRecursiveOn(child, action); } } public static void ForEach(Action 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 GetClassHierarchy(Type type) { while (type != typeof(Object) && type != null) { yield return type; type = type.BaseType; } } public static IEnumerable GetReverseClassHierarchy(Type type) => CUIComponent.GetClassHierarchy(type).Reverse(); #endregion #region Virtual -------------------------------------------------------- //TODO move to cui props, it's a bit more clampicated than ChildrenBoundaries /// /// Bounds for offset, e.g. scroll, zoom /// internal virtual CUIBoundaries ChildOffsetBounds => new CUIBoundaries(); /// /// "Component like" ghost stuff that can't have children and /// doesn't impact layout. Drag handles, text etc /// internal virtual void UpdatePseudoChildren() { LeftResizeHandle.Update(); RightResizeHandle.Update(); } /// /// Last chance to disagree with proposed size /// For stuff that should resize to content /// /// proposed size /// size you're ok with internal virtual Vector2 AmIOkWithThisSize(Vector2 size) => size; /// /// Here component should be drawn /// /// public virtual partial void Draw(SpriteBatch spriteBatch); /// /// Method for drawing something that should always be on top, e.g. resize handles /// /// 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(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 } }