C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
namespace Oxide.Plugins
{
[Info("UI Builder Library", "BlueBeka", "0.0.7")]
[Description("Allows for easily creating complex UIs.")]
public class UiBuilderLibrary : CovalencePlugin
{
private static UiBuilderLibrary SelfRef;
private PluginConfig config;
private PluginData data;
#region Hooks
private void Init()
{
#if DEBUG
if (SelfRef != null)
Interface.Oxide.LogWarning("Self already defined.");
#endif
SelfRef = this;
config = new PluginConfig(this);
data = new PluginData(this);
}
private void Loaded()
{
LoadConfig();
data.Load();
}
private void OnServerSave()
{
data.Save();
}
private void Unload()
{
data.Save();
foreach (var ui in UI.GetAllUis())
ui.CloseAll();
}
private void OnUserDisconnected(IPlayer player)
{
foreach (var ui in UI.GetAllUis())
ui.Close((BasePlayer)player.Object, false);
}
protected override void SaveConfig()
{
base.SaveConfig();
config.Save();
}
protected override void LoadConfig()
{
base.LoadConfig();
config.Load();
}
#endregion Hooks
#region Config and Data
/// <summary>
/// The config for this plugin./// </summary>
private class PluginConfig
{
// The plugin.private readonly UiBuilderLibrary Plugin;
public ConfigData Data;
public PluginConfig(UiBuilderLibrary plugin)
{
Plugin = plugin;
}
/// <summary>
/// Save the config data./// </summary>
public void Save()
{
if (Data == null)
{
Load();
}
else
{
SaveSerializable(Data);
}
}
/// <summary>
/// Load the config data./// </summary>
public void Load()
{
Data = LoadSerializable<ConfigData>();
}
/// <summary>
/// Save a serializable config object to a file./// </summary>
private void SaveSerializable<T>(T sData)
{
SaveSerializable(null, sData);
}
/// <summary>
/// Save a serializable config object to a file./// </summary>
private void SaveSerializable<T>(string file, T sData)
{
Plugin.Config.WriteObject(sData, false, GetFilename(file));
}
/// <summary>
/// Load a serializable config object from a file./// </summary>
private T LoadSerializable<T>()
{
return LoadSerializable<T>(null);
}
/// <summary>
/// Load a serializable config object from a file./// </summary>
private T LoadSerializable<T>(string file)
{
var result = Plugin.Config.ReadObject<T>(GetFilename(file));
SaveSerializable(file, result);
return result;
}
/// <summary>
/// Get the longer filename path for the file./// </summary>
private string GetFilename(string file)
{
return string.IsNullOrEmpty(file) ? $"oxide/config/{Plugin.Name}.json" : $"oxide/config/{Plugin.Name}/{file}.json";
}
/// <summary>
/// The config data for this plugin./// </summary>
public class ConfigData
{
public double DefaultScreenAspectRatio = 16.0 / 9.0;
public double DefaultRenderScale = 1.0;
}
}
/// <summary>
/// Everything related to the data that this plugin stores./// </summary>
private class PluginData
{
// The filename to save to.private readonly string Filename;
// The actual data.public Dictionary<ulong, Structure> PlayerData { get; protected set; }
public PluginData(UiBuilderLibrary plugin, string file = null)
{
Filename = string.IsNullOrEmpty(file) ? plugin.Name : $"{plugin.Name}/{file}";
}
/// <summary>
/// The data structure this plugin stores./// </summary>
public class Structure
{
public double ScreenAspectRatio;
public double RenderScale;
}
/// <summary>
/// Save the data./// </summary>
public void Save()
{
Interface.Oxide.DataFileSystem.WriteObject(Filename, PlayerData);
}
/// <summary>
/// Load the data./// </summary>
public void Load()
{
PlayerData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, Structure>>(Filename);
}
}
/// <summary>
/// Get the player data for the given player./// This data can then be mutated./// </summary>
/// <param name="player"></param>
/// <param name="store">If the player doesn't already have any data, store a new copy.</param>
/// <returns></returns>
private PluginData.Structure GetPlayerData(BasePlayer player, bool store = true)
{
if (data.PlayerData.ContainsKey(player.userID))
return data.PlayerData[player.userID];
var playerData = new PluginData.Structure()
{
RenderScale = config.Data.DefaultRenderScale,
ScreenAspectRatio = config.Data.DefaultScreenAspectRatio,
};
if (store)
{
data.PlayerData[player.userID] = playerData;
}
return playerData;
}
#endregion Config and Data
#region API methods
/// <summary>
/// Get the Screen Aspect Ratio for the given player./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public double GetScreenAspectRatio(BasePlayer player)
{
var playerData = GetPlayerData(player, false);
return playerData.ScreenAspectRatio;
}
/// <summary>
/// Sets the Screen Aspect Ratio for the given player./// </summary>
/// <param name="player"></param>
/// <param name="value"></param>
public void SetScreenAspectRatio(BasePlayer player, double value)
{
var playerData = GetPlayerData(player);
playerData.ScreenAspectRatio = value;
}
/// <summary>
/// Get the Render Scale for the given player./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public double GetRenderScale(BasePlayer player)
{
var playerData = GetPlayerData(player, false);
return playerData.RenderScale;
}
/// <summary>
/// Sets the Render Scale for the given player./// </summary>
/// <param name="player"></param>
/// <param name="value"></param>
public void SetRenderScale(BasePlayer player, double value)
{
var playerData = GetPlayerData(player);
playerData.RenderScale = value;
}
#endregion API methods
#region API classes
public class UI
{
private static readonly List<WeakReference<UI>> AllUis = new List<WeakReference<UI>>();
public const string FontBold = "robotocondensed-bold.ttf";
public const string FontRegular = "robotocondensed-regular.ttf";
public const string FontMono = "droidsansmono.ttf";
public const string FontMarker = "permanentmarker.ttf";
private RootElement Root { get; }
/// <summary>
/// Create a new UI./// </summary>
/// <param name="parentId">The parent of this UI.For top-level UIs, one of: "Overlay", "Hud.Menu", "Hud" or "Under"</param>
/// <param name="rootBuilder">Callback function that builds the UI.</param>
public UI(string parentId, Func<RootElement.Instance, bool> rootBuilder)
{
Root = new RootElement(parentId, rootBuilder);
AllUis.Add(new WeakReference<UI>(this));
}
/// <summary>
/// Get a collection of all the UIs that exist./// </summary>
/// <returns></returns>
internal static IEnumerable<UI> GetAllUis()
{
var foundUis = new List<UI>();
var missingUis = new HashSet<WeakReference<UI>>();
foreach (var uiRef in AllUis)
{
UI ui = uiRef.Target;
if (ui != null)
foundUis.Add(ui);
else
missingUis.Add(uiRef);
}
if (missingUis.Count > 0)
AllUis.RemoveAll(ui => missingUis.Contains(ui));
return foundUis;
}
/// <summary>
/// Open this UI for the given player./// </summary>
/// <param name="player"></param>
public void Open(BasePlayer player)
{
var updatedElements = new Collection<Element.Instance>();
Root.Open(player, updatedElements, false);
var rootIds = GetRootElementIds(updatedElements);
var jsonObjects = updatedElements.SelectMany(element => ToJson(element, rootIds)).ToArray();
if (jsonObjects.Length == 0)
{
#if DEBUG
Interface.Oxide.LogDebug("Nothing to display/remove.");
#endif
return;
}
var json = $"[{string.Join(",", jsonObjects)}]";
CuiHelper.AddUi(player, json);
}
/// <summary>
/// Close this UI for the given player./// </summary>
/// <param name="player"></param>
/// <param name="sendCommand">Send a command to the client to close the UI.</param>
public void Close(BasePlayer player, bool sendCommand = true)
{
var id = Root.Close(player);
if (!sendCommand)
return;
CuiHelper.DestroyUi(player, id);
}
/// <summary>
/// Close this UI for all players./// </summary>
public void CloseAll()
{
foreach (BasePlayer player in BasePlayer.activePlayerList)
Close(player);
}
/// <summary>
/// Get the Screen Aspect Ratio for the given player./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public static double GetScreenAspectRatio(BasePlayer player)
{
return SelfRef.GetScreenAspectRatio(player);
}
/// <summary>
/// Set the Screen Aspect Ratio for the given player./// </summary>
/// <param name="player"></param>
/// <param name="value"></param>
/// <returns></returns>
public static void SetScreenAspectRatio(BasePlayer player, double value)
{
SelfRef.SetScreenAspectRatio(player, value);
}
/// <summary>
/// Get the Render Scale for the given player./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public static double GetRenderScale(BasePlayer player)
{
return SelfRef.GetRenderScale(player);
}
/// <summary>
/// Set the Render Scale for the given player./// </summary>
/// <param name="player"></param>
/// <param name="value"></param>
/// <returns></returns>
public static void SetRenderScale(BasePlayer player, double value)
{
SelfRef.SetRenderScale(player, value);
}
/// <summary>
/// Get a JSON representation of the given Element./// </summary>
/// <param name="element">What to encode.</param>
/// <param name="roots">List of the root element ids.</param>
/// <returns></returns>
private static IEnumerable<string> ToJson(Element.Instance element, HashSet<string> roots)
{
var cuiElements = element.GetCuiElements();
var settings = new JsonSerializerSettings();
settings.DefaultValueHandling = DefaultValueHandling.Ignore;
return cuiElements.Select(cuiElement =>
{
if (element.Visible)
{
var json = JsonConvert.SerializeObject(cuiElement, Formatting.None, settings).Replace("\\n", "\n");
if (roots.Contains(cuiElement.Name))
return $"{{\"destroyUi\":\"{cuiElement.Name}\",{json.Substring(1)}";
return json;
}
return $"{{\"destroyUi\":\"{cuiElement.Name}\",\"name\":\"{cuiElement.Name}\"}}";
});
}
/// <summary>
/// Get a collection of the root element ids from the given collection of elements./// </summary>
/// <param name="elements"></param>
/// <returns></returns>
private static HashSet<string> GetRootElementIds(IEnumerable<Element.Instance> elements)
{
var names = new HashSet<string>(elements.Select(element => element.Id));
return new HashSet<string>(elements.Where(element => !names.Contains(element.GetParentId())).Select(element => element.Id));
}
}
/// <summary>
/// The base class of all elements./// </summary>
public abstract class Element
{
protected readonly HardWeakValueDictionary<ulong, Instance> InstanceCache = new HardWeakValueDictionary<ulong, Instance>();
// `Parent` and `ParentId` should not both be set.private readonly string ParentId;
private readonly Element Parent;
internal Element(Element parent)
{
Parent = parent;
ParentId = null;
}
internal Element(string parentId)
{
Parent = null;
ParentId = parentId;
}
/// <summary>
/// Get the instance of this element for the given player./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public T GetInstance<T>(BasePlayer player) where T : Instance
{
Instance instance = null;
return InstanceCache.TryGetValueAndMakeHard(player.userID, out instance) ? (T)instance : null;
}
/// <summary>
/// Open this element for the given player./// </summary>
/// <param name="player"></param>
/// <param name="updatedElements"></param>
/// <param name="parentHasUpdates"></param>
/// <returns>The id of the element instance that was opened.</returns>
public virtual string Open(BasePlayer player, Collection<Element.Instance> updatedElements, bool parentHasUpdates)
{
InstanceCache[player.userID].Open(updatedElements, parentHasUpdates);
return InstanceCache[player.userID].Id;
}
/// <summary>
/// Close this element for the given player./// </summary>
/// <param name="player"></param>
/// <returns>The id of the element instance that was closed.</returns>
public string Close(BasePlayer player)
{
if (!InstanceCache.ContainsKey(player.userID))
return null;
var instance = InstanceCache.GetValueAndMakeWeak(player.userID); ;
instance.Close();
return instance.Id;
}
/// <summary>
/// An instance of the element for an indiviual player./// </summary>
public abstract class Instance
{
public string Id { get; }
protected readonly Element Element;
protected readonly Instance Parent;
public BasePlayer Player { get; private set; }
public BoundingBox Bounds { get; private set; }
public bool Visible { get; set; } = true;
protected readonly List<Element> Children = new List<Element>();
private int BuildingChildIndex;
protected bool IsOpen;
protected bool Initialized;
public Instance(Element element, BasePlayer player)
{
Id = CuiHelper.GetGuid();
Element = element;
Player = player;
Parent = Element.Parent == null ? null : Element.Parent.GetInstance<Instance>(Player);
Bounds = new BoundingBox(Parent);
Initialized = false;
}
/// <summary>
/// Get the id of this element's parent./// </summary>
/// <returns></returns>
public string GetParentId()
{
return Parent?.Id ?? Element.ParentId;
}
/// <summary>
/// Open this element instance and its children./// </summary>
/// <param name="player"></param>
/// <param name="updatedElements"></param>
/// <param name="parentHasUpdates"></param>
/// <exception cref="Exception"></exception>
public void Open(Collection<Instance> updatedElements, bool parentHasUpdates)
{
BuildingChildIndex = 0;
var hasUpdates = Render() || parentHasUpdates || !IsOpen;
if (hasUpdates)
updatedElements.Add(this);
#if DEBUG
if (Initialized && Visible && BuildingChildIndex != Children.Count)
throw new Exception($"[{GetType().FullName}] Different number of children after update ({Children.Count} => {BuildingChildIndex}).");
#endif
if (Visible)
foreach (var child in Children)
child.Open(Player, updatedElements, hasUpdates);
IsOpen = true;
Initialized = true;
}
/// <summary>
/// Render this element./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public abstract bool Render();
/// <summary>
/// Mark this instance as closed Clear any internally stored data./// </summary>
public void Close()
{
IsOpen = false;
foreach (var child in Children)
child.Close(Player);
}
/// <summary>
/// Add a child to this element./// If the element has already been initialized, use the other version of this function./// </summary>
/// <param name="element"></param>
protected T AddChild<T>(T element) where T : Element
{
#if DEBUG
if (Initialized)
throw new Exception("Cannot add child once initialized.");
if (element == null)
throw new Exception("Cannot add null as child.");
#endif
Children.Add(element);
return element;
}
/// <summary>
/// Mark that a child has been added to this element./// If the element has not yet been initialized, use the other version of this function./// </summary>
/// <typeparam name="T">The type of the child.</typeparam>
/// <returns>The child.</returns>
protected T AddChild<T>() where T : Element
{
#if DEBUG
if (!Initialized)
throw new Exception("Not yet initialized.");
#endif
var element = (T)Children[BuildingChildIndex];
BuildingChildIndex++;
return element;
}
/// <summary>
/// Get all the CuiElements for this element./// </summary>
/// <returns></returns>
public abstract IEnumerable<CuiElement> GetCuiElements();
}
}
/// <summary>
/// The top level element./// </summary>
public class RootElement : PanelElement
{
protected internal Func<Instance, bool> Renderer { get; set; }
internal RootElement(string parentId, Func<Instance, bool> renderer) : base(parentId)
{
Renderer = renderer;
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
return instance;
instance = new Instance(this, player);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
/// <summary>
/// Open this element for the given player./// </summary>
/// <param name="player"></param>
/// <param name="updatedElements"></param>
/// <param name="parentHasUpdates"></param>
/// <returns>The id of the element instance that was opened.</returns>
public override string Open(BasePlayer player, Collection<Element.Instance> updatedElements, bool parentHasUpdates)
{
var instance = EnsureInstance(player);
instance.Open(updatedElements, parentHasUpdates);
return instance.Id;
}
public new class Instance : PanelElement.Instance
{
internal Instance(Element element, BasePlayer player) : base(element, player, null)
{
}
public override bool Render()
{
var renderer = ((RootElement)Element).Renderer;
return renderer(this);
}
}
}
/// <summary>
/// A Panel./// </summary>
public class PanelElement : Element
{
internal PanelElement(Element parent) : base(parent)
{
}
internal PanelElement(string parentId) : base(parentId)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : Element.Instance
{
public CuiImageComponent Image { get; } = new CuiImageComponent();
public bool CursorEnabled { get; set; }
public bool KeyboardEnabled { get; set; }
protected internal Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player)
{
Renderer = renderer;
}
public override bool Render()
{
return Renderer(this);
}
public override IEnumerable<CuiElement> GetCuiElements()
{
CuiElement cuiElement = new CuiElement()
{
Name = Id,
Parent = GetParentId(),
};
cuiElement.Components.Add(Image);
cuiElement.Components.Add(Bounds.GetCuiComponent());
if (CursorEnabled)
cuiElement.Components.Add(new CuiNeedsCursorComponent());
if (KeyboardEnabled)
cuiElement.Components.Add(new CuiNeedsKeyboardComponent());
return new CuiElement[] { cuiElement };
}
/// <summary>
/// Add a subpanel to this panel./// </summary>
/// <param name="renderer"></param>
public void AddPanel(Func<Instance, bool> renderer)
{
PanelElement element = Initialized ? AddChild<PanelElement>() : AddChild(new PanelElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add text to this panel./// </summary>
/// <param name="renderer"></param>
public void AddLabel(Func<LabelElement.Instance, bool> renderer)
{
LabelElement element = Initialized ? AddChild<LabelElement>() : AddChild(new LabelElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add a button to this panel./// </summary>
/// <param name="renderer"></param>
public void AddButton(Func<ButtonElement.Instance, bool> renderer)
{
ButtonElement element = Initialized ? AddChild<ButtonElement>() : AddChild(new ButtonElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add an in-game image to this panel.///
/// Use `AddRawImage` to add a custom image./// </summary>
/// <param name="renderer"></param>
public void AddGameImage(Func<GameImageElement.Instance, bool> renderer)
{
GameImageElement element = Initialized ? AddChild<GameImageElement>() : AddChild(new GameImageElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add a custom image to this panel./// </summary>
/// <param name="renderer"></param>
public void AddRawImage(Func<RawImageElement.Instance, bool> renderer)
{
RawImageElement element = Initialized ? AddChild<RawImageElement>() : AddChild(new RawImageElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add an input field to this panel./// </summary>
/// <param name="renderer"></param>
public void AddInputField(Func<InputFieldElement.Instance, bool> renderer)
{
InputFieldElement element = Initialized ? AddChild<InputFieldElement>() : AddChild(new InputFieldElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add a 1D group of buttons to this panel./// </summary>
/// <param name="renderer"></param>
public void AddTabs(Func<TabsElement.Instance, bool> renderer)
{
TabsElement element = Initialized ? AddChild<TabsElement>() : AddChild(new TabsElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add a 2D group of panel layed out in a grid to this panel.///
/// Be sure to add the correct number of cells (based on row and column count) to this grid./// </summary>
/// <param name="renderer"></param>
public void AddGrid(Func<GridElement.Instance, bool> renderer)
{
GridElement element = Initialized ? AddChild<GridElement>() : AddChild(new GridElement(Element));
element.EnsureInstance(Player, renderer);
}
/// <summary>
/// Add a countdown component./// </summary>
/// <param name="renderer"></param>
public void AddCountdown(Func<CountdownElement.Instance, bool> renderer)
{
CountdownElement element = Initialized ? AddChild<CountdownElement>() : AddChild(new CountdownElement(Element));
element.EnsureInstance(Player, renderer);
}
}
}
/// <summary>
/// A Label
/// </summary>
public class LabelElement : Element
{
internal LabelElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : Element.Instance
{
public CuiTextComponent Text { get; } = new CuiTextComponent();
public CuiOutlineComponent Outline { get; } = new CuiOutlineComponent();
protected internal Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player)
{
Renderer = renderer;
}
public override bool Render()
{
return Renderer(this);
}
public override IEnumerable<CuiElement> GetCuiElements()
{
CuiElement cuiElement = new CuiElement()
{
Name = Id,
Parent = GetParentId(),
};
if (!string.IsNullOrEmpty(Text?.Text))
cuiElement.Components.Add(Text);
cuiElement.Components.Add(Bounds.GetCuiComponent());
if (!string.IsNullOrEmpty(Outline.Color))
cuiElement.Components.Add(Outline);
return new CuiElement[] { cuiElement };
}
}
}
/// <summary>
/// A Text Button
/// </summary>
public class ButtonElement : Element
{
internal ButtonElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : Element.Instance
{
public CuiButtonComponent Button { get; } = new CuiButtonComponent();
public CuiTextComponent Text { get; } = new CuiTextComponent();
protected internal Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player)
{
Renderer = renderer;
}
public override bool Render()
{
return Renderer(this);
}
public override IEnumerable<CuiElement> GetCuiElements()
{
var cuiElements = new List<CuiElement>
{
new CuiElement()
{
Name = Id,
Parent = GetParentId(),
Components =
{
Button,
Bounds.GetCuiComponent()
}
}
};
if (!string.IsNullOrEmpty(Text?.Text))
{
cuiElements.Add(new CuiElement
{
Name = CuiHelper.GetGuid(),
Parent = Id,
Components = {
Text,
new CuiRectTransformComponent()
}
});
}
return cuiElements;
}
}
}
/// <summary>
/// A raw image./// </summary>
public class RawImageElement : Element
{
internal RawImageElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : Element.Instance
{
public CuiRawImageComponent Image { get; } = new CuiRawImageComponent();
protected internal Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player)
{
Renderer = renderer;
}
public override bool Render()
{
return Renderer(this);
}
public override IEnumerable<CuiElement> GetCuiElements()
{
CuiElement cuiElement = new CuiElement()
{
Name = Id,
Parent = GetParentId(),
Components = {
Image,
Bounds.GetCuiComponent()
}
};
return new CuiElement[] { cuiElement };
}
}
}
/// <summary>
/// An game image./// </summary>
public class GameImageElement : Element
{
internal GameImageElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : Element.Instance
{
public CuiImageComponent Image { get; } = new CuiImageComponent();
protected internal Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player)
{
Renderer = renderer;
}
public override bool Render()
{
return Renderer(this);
}
public override IEnumerable<CuiElement> GetCuiElements()
{
CuiElement cuiElement = new CuiElement()
{
Name = Id,
Parent = GetParentId(),
Components = {
Image,
Bounds.GetCuiComponent()
}
};
return new CuiElement[] { cuiElement };
}
}
}
/// <summary>
/// A text input field./// </summary>
public class InputFieldElement : Element
{
internal InputFieldElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : Element.Instance
{
public CuiInputFieldComponent Input { get; } = new CuiInputFieldComponent()
{
NeedsKeyboard = true,
};
protected internal Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player)
{
Renderer = renderer;
}
public override bool Render()
{
return Renderer(this);
}
public override IEnumerable<CuiElement> GetCuiElements()
{
CuiElement cuiElement = new CuiElement()
{
Name = Id,
Parent = GetParentId(),
Components = {
Input,
Bounds.GetCuiComponent()
}
};
return new CuiElement[] { cuiElement };
}
}
}
/// <summary>
/// Text Tabs
/// </summary>
public class TabsElement : PanelElement
{
internal TabsElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : PanelElement.Instance
{
public bool Vertical { get; set; }
public double Gap { get; set; }
public double MaxButtonSize { get; set; }
private int _BuildingTabIndex;
private int TabsCount;
protected internal new Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player, null)
{
Renderer = renderer;
}
public override bool Render()
{
_BuildingTabIndex = 0;
return Renderer(this);
}
/// <summary>
/// Add a tab (button) to this set of tabs (buttons)./// </summary>
/// <param name="renderer"></param>
public void AddTab(Func<ButtonElement.Instance, bool> renderer)
{
if (!Initialized)
{
TabsCount++;
}
AddButton((button) =>
{
SetTabDefaults(button);
_BuildingTabIndex++;
return renderer(button);
});
}
private void SetTabDefaults(ButtonElement.Instance button)
{
var gapSize = Gap;
var buttonSize = 1.0 / TabsCount - gapSize * (TabsCount - 1) / TabsCount;
buttonSize = MaxButtonSize > 0 ? Math.Min(buttonSize, MaxButtonSize) : buttonSize;
var offset = (double)_BuildingTabIndex / TabsCount * (1 + gapSize);
if (Vertical)
{
button.Bounds.MinX = 0;
button.Bounds.MaxX = 1;
button.Bounds.MinY = 1 - offset - buttonSize;
button.Bounds.MaxY = 1 - offset;
}
else
{
button.Bounds.MinX = offset;
button.Bounds.MaxX = offset + buttonSize;
button.Bounds.MinY = 0;
button.Bounds.MaxY = 1;
}
}
}
}
/// <summary>
/// A Grid of panels
/// </summary>
public class GridElement : PanelElement
{
internal GridElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : PanelElement.Instance
{
public double GapX { get; set; }
public double GapY { get; set; }
public int Rows { get; set; } = 2;
public int Columns { get; set; } = 2;
private int _BuildingCellIndex;
protected internal new Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player, null)
{
Renderer = renderer;
}
public override bool Render()
{
_BuildingCellIndex = 0;
return Renderer(this);
}
/// <summary>
/// Add a cell (panel) to this grid./// </summary>
/// <param name="renderer"></param>
public void AddCell(Func<PanelElement.Instance, bool> renderer)
{
AddPanel((cell) =>
{
SetCellDefaults(cell);
_BuildingCellIndex++;
return renderer(cell);
});
}
private void SetCellDefaults(PanelElement.Instance cell)
{
var column = _BuildingCellIndex % Columns;
var width = 1.0 / Columns - GapX * (Columns - 1) / Columns;
var offsetX = (double)column / Columns * (1 + GapX);
var row = _BuildingCellIndex / Columns;
var height = 1.0 / Rows - GapY * (Rows - 1) / Rows;
var offsetY = (double)row / Rows * (1 + GapY);
cell.Bounds.MinX = offsetX;
cell.Bounds.MaxX = offsetX + width;
cell.Bounds.MinY = 1 - offsetY - height;
cell.Bounds.MaxY = 1 - offsetY;
}
}
}
/// <summary>
/// A count down./// </summary>
public class CountdownElement : Element
{
internal CountdownElement(Element parent) : base(parent)
{
}
/// <summary>
/// Ensure that an instance of this element exists for the given player./// </summary>
/// <param name="player"></param>
/// <param name="renderer"></param>
/// <returns></returns>
public Instance EnsureInstance(BasePlayer player, Func<Instance, bool> renderer)
{
var instance = GetInstance<Instance>(player);
if (instance != null)
{
instance.Renderer = renderer;
return instance;
}
instance = new Instance(this, player, renderer);
InstanceCache.Add(player.userID, instance, true);
return instance;
}
public new class Instance : Element.Instance
{
public CuiCountdownComponent Countdown { get; } = new CuiCountdownComponent();
protected internal Func<Instance, bool> Renderer { get; set; }
internal Instance(Element element, BasePlayer player, Func<Instance, bool> renderer) : base(element, player)
{
Renderer = renderer;
}
public override bool Render()
{
return Renderer(this);
}
public override IEnumerable<CuiElement> GetCuiElements()
{
CuiElement cuiElement = new CuiElement()
{
Name = Id,
Parent = GetParentId(),
Components = {
Countdown,
}
};
return new CuiElement[] { cuiElement };
}
}
}
public class BoundingBox
{
private readonly Element.Instance Parent;
public double MinX { get; set; } = 0;
public double MinY { get; set; } = 0;
public double MaxX { get; set; } = 1;
public double MaxY { get; set; } = 1;
public BoundingBox(Element.Instance parent)
{
Parent = parent;
}
/// <summary>
/// Gets the aspect ratio of this element on the given player's screen./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public double GetAspectRatio(BasePlayer player)
{
return GetRelativeWidth(player) / GetRelativeHeight(player);
}
/// <summary>
/// Get the width of this element (as a percentage of its parent's width)./// </summary>
/// <returns></returns>
public double GetWidth()
{
return MaxX - MinX;
}
/// <summary>
/// Get the height of this element (as a percentage of its parent's height)./// </summary>
/// <returns></returns>
public double GetHeight()
{
return MaxY - MinY;
}
/// <summary>
/// Get the width of this element as a percentage of the player's screen width./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public double GetRelativeWidth(BasePlayer player)
{
if (Parent == null)
{
return GetWidth();
}
return GetWidth() * Parent.Bounds.GetRelativeWidth(player);
}
/// <summary>
/// Get the height of this element as a percentage of the player's screen **width** (not height)./// </summary>
/// <param name="player"></param>
/// <returns></returns>
public double GetRelativeHeight(BasePlayer player)
{
if (Parent == null)
{
return GetHeight() / UI.GetScreenAspectRatio(player);
}
return GetHeight() * Parent.Bounds.GetRelativeHeight(player);
}
/// <summary>
/// Get the `CuiRectTransformComponent` this object represents./// </summary>
/// <returns></returns>
public CuiRectTransformComponent GetCuiComponent()
{
return new CuiRectTransformComponent()
{
AnchorMin = $"{MinX} {MinY}",
AnchorMax = $"{MaxX} {MaxY}",
};
}
}
#endregion API classes
#region Collections
/// <summary>
/// A dictionary in which the values can be marked as weak references./// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
public class HardWeakValueDictionary<TKey, TValue> where TValue : class // : IDictionary<TKey, TValue>
{
private Dictionary<TKey, HardWeakReference<TValue>> _dict = new Dictionary<TKey, HardWeakReference<TValue>>();
private int _version, _cleanVersion;
private int _cleanGeneration;
private const int MinRehashInterval = 500;
public ICollection<TKey> Keys => _dict.Keys;
public ICollection<TValue> Values
{ get { throw new NotImplementedException(); } }
public int Count => _dict.Count;
public bool IsReadOnly => false;
public TValue this[TKey key]
{
get
{
#if DEBUG
if (!_dict[key].IsHard)
throw new Exception("Do not access via index when weak.");
#endif
return _dict[key].GetTarget();
}
set
{
_dict[key] = new HardWeakReference<TValue>(value);
}
}
public HardWeakValueDictionary()
{
}
public void Add(TKey key, TValue value)
{
Add(key, value, true);
}
public void Add(TKey key, TValue value, bool hard)
{
AutoCleanup(2);
HardWeakReference<TValue> reference;
if (_dict.TryGetValue(key, out reference))
{
if (reference.IsAlive)
throw new ArgumentException("An element with the same key already exists in this dictionary");
reference.SetTarget(value);
if (hard)
reference.MakeHard();
return;
}
_dict.Add(key, new HardWeakReference<TValue>(value, hard));
}
public bool MakeWeak(TKey key)
{
AutoCleanup(1);
HardWeakReference<TValue> reference;
if (!_dict.TryGetValue(key, out reference))
return false;
return reference.MakeWeak();
}
public bool MakeHard(TKey key)
{
AutoCleanup(1);
HardWeakReference<TValue> reference;
if (!_dict.TryGetValue(key, out reference))
return false;
return reference.MakeHard();
}
public bool ContainsKey(TKey key)
{
AutoCleanup(1);
HardWeakReference<TValue> value;
if (!_dict.TryGetValue(key, out value))
return false;
return value.IsAlive;
}
public bool Remove(TKey key)
{
AutoCleanup(1);
HardWeakReference<TValue> reference;
if (!_dict.TryGetValue(key, out reference))
return false;
_dict.Remove(key);
return reference.IsAlive;
}
public bool TryGetValue(TKey key, out TValue value)
{
AutoCleanup(1);
HardWeakReference<TValue> reference;
if (_dict.TryGetValue(key, out reference))
value = reference.GetTarget();
else
value = null;
return value != null;
}
public bool TryGetValueAndMakeHard(TKey key, out TValue value)
{
AutoCleanup(1);
HardWeakReference<TValue> reference;
if (_dict.TryGetValue(key, out reference))
value = reference.GetTarget();
else
value = null;
if (value == null)
return false;
return reference.MakeHard();
}
public TValue GetValueAndMakeWeak(TKe
// ... [Code preview truncated due to length. Full source in attached file] ...