Add workshop things
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="CsForBarotrauma" steamworkshopid="2795927223" corepackage="false" modversion="1.0.1" gameversion="0.17.12.0" installtime="1650551414" altnames="CsForBarotrauma" expectedhash="D41D8CD98F00B204E9800998ECF8427E" />
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Items>
|
||||||
|
<override>
|
||||||
|
<Item name="" identifier="endocrinebooster" category="Medical" maxstacksize="8" Tags="smallitem,chem,medical,syringe" allowasextracargo="true" description="" useinhealthinterface="true" scale="0.5" impactsoundtag="impact_metal_light">
|
||||||
|
<PreferredContainer primary="medcab" />
|
||||||
|
<Price baseprice="1400" sold="false">
|
||||||
|
<Price storeidentifier="merchantoutpost" />
|
||||||
|
<Price storeidentifier="merchantcity" multiplier="0.9" />
|
||||||
|
<Price storeidentifier="merchantresearch" multiplier="1.1" />
|
||||||
|
<Price storeidentifier="merchantmilitary" multiplier="1.1" />
|
||||||
|
<Price storeidentifier="merchantmine" />
|
||||||
|
</Price>
|
||||||
|
<Fabricate suitablefabricators="medicalfabricator" requiredtime="50" amount="1" requiresrecipe="true">
|
||||||
|
<RequiredSkill identifier="medical" level="90" />
|
||||||
|
<RequiredItem identifier="sulphuriteshard" />
|
||||||
|
<RequiredItem identifier="sulphuriteshard" />
|
||||||
|
<RequiredItem identifier="paralyxis" />
|
||||||
|
<RequiredItem identifier="paralyxis" />
|
||||||
|
<RequiredItem identifier="deusizine" />
|
||||||
|
</Fabricate>
|
||||||
|
<Deconstruct time="25">
|
||||||
|
<Item identifier="sulphuricacid" />
|
||||||
|
<Item identifier="sulphuricacid" />
|
||||||
|
<Item identifier="paralyxis" />
|
||||||
|
<Item identifier="deusizine" />
|
||||||
|
</Deconstruct>
|
||||||
|
<InventoryIcon texture="Content/Items/Medical/Medicines.png" sourcerect="448,256,64,64" origin="0.5,0.5" />
|
||||||
|
<Sprite texture="Content/Items/Medical/Medicines.png" sourcerect="303,310,35,67" depth="0.6" origin="0.5,0.5" />
|
||||||
|
<Body width="35" height="65" density="15" />
|
||||||
|
<MeleeWeapon canBeCombined="true" removeOnCombined="true" slots="Any,RightHand,LeftHand" aimpos="40,5" handle1="-5,0" holdangle="220" reload="1.0" msg="ItemMsgPickUpSelect">
|
||||||
|
<RequiredSkill identifier="medical" level="35" />
|
||||||
|
<StatusEffect type="OnSuccess" target="This" Condition="-100.0" disabledeltatime="true">
|
||||||
|
<Sound file="Content/Items/Medical/Syringe.ogg" range="500" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnFailure" target="This" Condition="-100.0" disabledeltatime="true">
|
||||||
|
<Sound file="Content/Items/Medical/Syringe.ogg" range="500" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect tags="medical" type="OnSuccess" target="Character">
|
||||||
|
<GiveTalentInfo giverandom="true" talentidentifiers="bountyhunter,logisticsexpert,prodigy,inspirationalleader,veteran,travelingtradesman,emergencymaneuvers,sailorwithnoname,camaraderie,downwiththeship,gunsmith,steadytune,protectandserve,warstories,firstaidtraining,physicalconditioning,swole,buff,weaponsmith,bythebook,dontpushit,bootcamp,munitionsexpertise,playingcatchup,mailman,skedaddle,crewlayabout,revengesquad,inspiringtunes,jackinthebox,peerlearning,stationengineer,junctionjunkie,egghead,grounded,remotemonitor,funwithfission,melodicrespite,submarineofthings,aggressiveengineering,samplecollection,salvagecrew,machinemaniac,safetyfirst,multifunctional,engineengineer,ballastdenizen,modularrepairs,oiledmachinery,pumpndump,ironman,retrofit,healthinsurance,nobodyimportantdies,exampleofhealth,laresistance,selfcare,stayinalive,firemanscarry,medicalcompanion,dontdieonme,nopressure,blooddonor" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect tags="medical" type="OnFailure" target="Character">
|
||||||
|
<GiveTalentInfo giverandom="true" talentidentifiers="bountyhunter,logisticsexpert,prodigy,inspirationalleader,veteran,travelingtradesman,emergencymaneuvers,sailorwithnoname,camaraderie,downwiththeship,gunsmith,steadytune,protectandserve,warstories,firstaidtraining,physicalconditioning,swole,buff,weaponsmith,bythebook,dontpushit,bootcamp,munitionsexpertise,playingcatchup,mailman,skedaddle,crewlayabout,revengesquad,inspiringtunes,jackinthebox,peerlearning,stationengineer,junctionjunkie,egghead,grounded,remotemonitor,funwithfission,melodicrespite,submarineofthings,aggressiveengineering,samplecollection,salvagecrew,machinemaniac,safetyfirst,multifunctional,engineengineer,ballastdenizen,modularrepairs,oiledmachinery,pumpndump,ironman,retrofit,healthinsurance,nobodyimportantdies,exampleofhealth,laresistance,selfcare,stayinalive,firemanscarry,medicalcompanion,dontdieonme,nopressure,blooddonor" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnBroken" target="This">
|
||||||
|
<Remove />
|
||||||
|
</StatusEffect>
|
||||||
|
</MeleeWeapon>
|
||||||
|
<Projectile characterusable="false" launchimpulse="20.0" sticktocharacters="true" launchrotation="-90" inheritstatuseffectsfrom="MeleeWeapon" inheritrequiredskillsfrom="MeleeWeapon" />
|
||||||
|
</Item>
|
||||||
|
</override>
|
||||||
|
</Items>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="All The Talent Trees + Unlock Parallel Talents" steamworkshopid="2913302583" corepackage="false" modversion="1.8" gameversion="1.7.7.0" installtime="1741741350" expectedhash="F618C637659A402B567063A51350B000">
|
||||||
|
<TalentTrees file="%ModDir%/TalentTrees.xml" />
|
||||||
|
</contentpackage>
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
using Barotrauma.Items.Components;
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace BaroMod_sjx
|
||||||
|
{
|
||||||
|
partial class ItemBoxImpl
|
||||||
|
{
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Inventory), nameof(Inventory.DrawSlot))]
|
||||||
|
class Patch_DrawSlot
|
||||||
|
{
|
||||||
|
public class context
|
||||||
|
{
|
||||||
|
public SpriteBatch spriteBatch;
|
||||||
|
|
||||||
|
public Inventory inventory;
|
||||||
|
public Sprite? indicatorSprite;
|
||||||
|
public Sprite? emptyIndicatorSprite;
|
||||||
|
public Sprite? itemSprite;
|
||||||
|
public Rectangle conditionIndicatorArea;
|
||||||
|
|
||||||
|
public int max_value;
|
||||||
|
public int cur_value;
|
||||||
|
public Vector2 sprite_pos;
|
||||||
|
public float sprite_scale;
|
||||||
|
public float rotation;
|
||||||
|
public Color spriteColor;
|
||||||
|
public context(SpriteBatch sb, Inventory inv, Sprite? full, Sprite? empty, Sprite? item, Rectangle area, int max, int cur, Vector2 sp, float ss, float rot, Color sc)
|
||||||
|
{
|
||||||
|
spriteBatch = sb;
|
||||||
|
inventory = inv;
|
||||||
|
indicatorSprite = full;
|
||||||
|
emptyIndicatorSprite = empty;
|
||||||
|
itemSprite = item;
|
||||||
|
conditionIndicatorArea = area;
|
||||||
|
max_value = max;
|
||||||
|
cur_value = cur;
|
||||||
|
sprite_pos = sp;
|
||||||
|
sprite_scale = ss;
|
||||||
|
rotation = rot;
|
||||||
|
spriteColor = sc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static private void Invoke_DrawItemStateIndicator(
|
||||||
|
SpriteBatch spriteBatch, Inventory inventory,
|
||||||
|
Sprite indicatorSprite, Sprite emptyIndicatorSprite, Rectangle containedIndicatorArea, float containedState,
|
||||||
|
bool pulsate = false)
|
||||||
|
{
|
||||||
|
AccessTools.Method(typeof(Inventory), "DrawItemStateIndicator")!
|
||||||
|
.Invoke(null, new object[] { spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, containedIndicatorArea, containedState, pulsate });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Sprite? GetTargetSprite(ConditionStorage conditionStorage, Inventory iv)
|
||||||
|
{
|
||||||
|
Inventory.ItemSlot target_slot;
|
||||||
|
{
|
||||||
|
Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(iv)! as Inventory.ItemSlot[])!;
|
||||||
|
if (conditionStorage.slotIndex >= slots.Length)
|
||||||
|
{
|
||||||
|
DebugConsole.LogError($"ConditionStorage of {(iv.Owner as Item)!.Prefab.Identifier} specified index {conditionStorage.slotIndex} out of {slots.Length}!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
target_slot = slots[conditionStorage.slotIndex];
|
||||||
|
}
|
||||||
|
if (target_slot.Any())
|
||||||
|
{
|
||||||
|
Item i = target_slot.First();
|
||||||
|
return i.Prefab.InventoryIcon ?? i.Sprite;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix(out context? __state,
|
||||||
|
SpriteBatch spriteBatch, Inventory inventory, VisualSlot slot, Item item, int slotIndex)
|
||||||
|
{
|
||||||
|
if (inventory != null && item != null && get_componentsByType(item).TryGetValue(typeof(ConditionStorage), out List<ItemComponent>? comps))
|
||||||
|
{
|
||||||
|
ConditionStorage conditionStorage = (comps.First() as ConditionStorage)!;
|
||||||
|
if (!conditionStorage.showIcon && !conditionStorage.showCount)
|
||||||
|
{
|
||||||
|
__state = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Rectangle rect = slot.Rect;
|
||||||
|
rect.Location += slot.DrawOffset.ToPoint();
|
||||||
|
|
||||||
|
if (slot.HighlightColor.A > 0)
|
||||||
|
{
|
||||||
|
float inflateAmount = (slot.HighlightColor.A / 255.0f) * slot.HighlightScaleUpAmount * 0.5f;
|
||||||
|
rect.Inflate(rect.Width * inflateAmount, rect.Height * inflateAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemContainer = item.GetComponent<ItemContainer>();
|
||||||
|
|
||||||
|
Sprite? indicatorSprite;
|
||||||
|
Sprite? emptyIndicatorSprite;
|
||||||
|
Rectangle conditionIndicatorArea;
|
||||||
|
if (conditionStorage.showCount)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
if (itemContainer is null)
|
||||||
|
{
|
||||||
|
DebugConsole.LogError($"Item {item.Prefab.Identifier} has ConditionStorage but no ItemContainer!");
|
||||||
|
__state = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null)
|
||||||
|
{
|
||||||
|
__state = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int dir = slot.SubInventoryDir;
|
||||||
|
|
||||||
|
if (itemContainer.ShowContainedStateIndicator)
|
||||||
|
{
|
||||||
|
conditionIndicatorArea = new Rectangle(rect.X, rect.Bottom - (int)(10 * GUI.Scale), rect.Width, (int)(10 * GUI.Scale));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
conditionIndicatorArea = new Rectangle(
|
||||||
|
rect.X, dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - Inventory.ContainedIndicatorHeight,
|
||||||
|
rect.Width, Inventory.ContainedIndicatorHeight);
|
||||||
|
conditionIndicatorArea.Inflate(-4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GUIComponentStyle indicatorStyle = GUIStyle.GetComponentStyle("ContainedStateIndicator.Default")!;
|
||||||
|
indicatorSprite = indicatorStyle.GetDefaultSprite();
|
||||||
|
emptyIndicatorSprite = indicatorStyle.GetSprite(GUIComponent.ComponentState.Hover);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indicatorSprite = null;
|
||||||
|
emptyIndicatorSprite = null;
|
||||||
|
conditionIndicatorArea = new Rectangle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 itemPos;
|
||||||
|
float scale;
|
||||||
|
float rotation;
|
||||||
|
Sprite? item_sprite;
|
||||||
|
Color spriteColor;
|
||||||
|
|
||||||
|
if (conditionStorage.showIcon)
|
||||||
|
{
|
||||||
|
item_sprite = GetTargetSprite(conditionStorage, itemContainer.Inventory!);
|
||||||
|
if (item_sprite != null)
|
||||||
|
{
|
||||||
|
scale = Math.Min(Math.Min((rect.Width - 10) / item_sprite.size.X, (rect.Height - 10) / item_sprite.size.Y), 2.0f);
|
||||||
|
itemPos = rect.Center.ToVector2();
|
||||||
|
if (itemPos.Y > GameMain.GraphicsHeight)
|
||||||
|
{
|
||||||
|
itemPos.Y -= Math.Min(
|
||||||
|
(itemPos.Y + item_sprite.size.Y / 2 * scale) - GameMain.GraphicsHeight,
|
||||||
|
(itemPos.Y - item_sprite.size.Y / 2 * scale) - rect.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
rotation = 0.0f;
|
||||||
|
if (slot.HighlightColor.A > 0)
|
||||||
|
{
|
||||||
|
rotation = (float)Math.Sin(slot.HighlightTimer * MathHelper.TwoPi) * slot.HighlightTimer * 0.3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
spriteColor = item_sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scale = 1.0f;
|
||||||
|
rotation = 0.0f;
|
||||||
|
spriteColor = Color.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item_sprite = null;
|
||||||
|
scale = 1.0f;
|
||||||
|
rotation = 0.0f;
|
||||||
|
spriteColor = Color.White;
|
||||||
|
}
|
||||||
|
Vector2 center = rect.Center.ToVector2() + (new Vector2(conditionStorage.iconShiftX, conditionStorage.iconShiftY)) * slot.Rect.Size.ToVector2() * 0.5f;
|
||||||
|
__state = new context(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, item_sprite,
|
||||||
|
conditionIndicatorArea, conditionStorage.maxItemCount, conditionStorage.currentItemCount, center,
|
||||||
|
scale * conditionStorage.iconScale, rotation, spriteColor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
__state = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public static void Postfix(context? __state)
|
||||||
|
{
|
||||||
|
if (__state != null)
|
||||||
|
{
|
||||||
|
__state.itemSprite?.Draw(__state.spriteBatch, __state.sprite_pos, __state.spriteColor, __state.rotation, __state.sprite_scale);
|
||||||
|
if (__state.indicatorSprite != null && __state.emptyIndicatorSprite != null)
|
||||||
|
{
|
||||||
|
Invoke_DrawItemStateIndicator(__state.spriteBatch, __state.inventory, __state.indicatorSprite, __state.emptyIndicatorSprite, __state.conditionIndicatorArea,
|
||||||
|
__state.cur_value / (float)__state.max_value);
|
||||||
|
string info_text = $"{__state.cur_value}/{__state.max_value}";
|
||||||
|
float text_scale = 0.75f;
|
||||||
|
Vector2 info_size = GUIStyle.SmallFont.MeasureString(info_text) * text_scale;
|
||||||
|
GUIStyle.SmallFont.DrawString(__state.spriteBatch, info_text, __state.conditionIndicatorArea.Center.ToVector2() - (info_size * 0.5f), Color.White, 0.0f, Vector2.Zero, text_scale, SpriteEffects.None, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial class ConditionStorage : ItemComponent, IServerSerializable
|
||||||
|
{
|
||||||
|
private CoroutineHandle? resetPredictionCoroutine = null;
|
||||||
|
private int? last_server_update_count = null;
|
||||||
|
private float resetPredictionTimer = 1.0f;
|
||||||
|
|
||||||
|
float last_update_time = 0;
|
||||||
|
|
||||||
|
const double remove_time = 1.0;
|
||||||
|
|
||||||
|
class ItemStackedInfo
|
||||||
|
{
|
||||||
|
public Item target;
|
||||||
|
public Inventory removed_from;
|
||||||
|
public Character user;
|
||||||
|
public double timestamp;
|
||||||
|
public int slot;
|
||||||
|
public ItemStackedInfo(Item item, Character character, Inventory removedFrom, int from_slot)
|
||||||
|
{
|
||||||
|
target = item;
|
||||||
|
removed_from = removedFrom;
|
||||||
|
user = character;
|
||||||
|
slot = from_slot;
|
||||||
|
timestamp = Timing.TotalTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep a list of items removed on client side so that they can be put back into container after timeour
|
||||||
|
private List<ItemStackedInfo> removed = new List<ItemStackedInfo>();
|
||||||
|
|
||||||
|
|
||||||
|
void RemoveItem_track(Item item, Character user, Inventory removedFrom, int slot)
|
||||||
|
{
|
||||||
|
removed.Add(new ItemStackedInfo(item, user, removedFrom, slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateRemovedItems()
|
||||||
|
{
|
||||||
|
var copy = removed.CreateCopy();
|
||||||
|
foreach (var item in copy)
|
||||||
|
{
|
||||||
|
// updated from server to be removed
|
||||||
|
if (item.target.Removed)
|
||||||
|
{
|
||||||
|
removed.Remove(item);
|
||||||
|
}
|
||||||
|
// timeout for removed item. put it back.
|
||||||
|
else if (Timing.TotalTime - item.timestamp > remove_time)
|
||||||
|
{
|
||||||
|
if (!item.removed_from.TryPutItem(item.target, item.slot, false, false, item.user, false, false))
|
||||||
|
{
|
||||||
|
item.target.Drop(item.user, true, true);
|
||||||
|
}
|
||||||
|
removed.Remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removed.Any())
|
||||||
|
{
|
||||||
|
IsActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnCountPredictionChanged()
|
||||||
|
{
|
||||||
|
if (GameMain.Client == null || !last_server_update_count.HasValue) { return; }
|
||||||
|
if (resetPredictionCoroutine == null || !CoroutineManager.IsCoroutineRunning(resetPredictionCoroutine))
|
||||||
|
{
|
||||||
|
resetPredictionCoroutine = CoroutineManager.StartCoroutine(ResetPredictionAfterDelay());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<CoroutineStatus> ResetPredictionAfterDelay()
|
||||||
|
{
|
||||||
|
while (resetPredictionTimer > 0.0f)
|
||||||
|
{
|
||||||
|
resetPredictionTimer -= CoroutineManager.DeltaTime;
|
||||||
|
yield return CoroutineStatus.Running;
|
||||||
|
}
|
||||||
|
if (last_server_update_count.HasValue) { SetItemCount(last_server_update_count.Value, false); }
|
||||||
|
resetPredictionCoroutine = null;
|
||||||
|
yield return CoroutineStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||||
|
{
|
||||||
|
if (last_update_time <= sendingTime)
|
||||||
|
{
|
||||||
|
last_update_time = sendingTime;
|
||||||
|
last_server_update_count = msg.ReadRangedInteger(0, maxItemCount);
|
||||||
|
SetItemCount(last_server_update_count.Value, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// discard the number, but still extract it from stream.
|
||||||
|
msg.ReadRangedInteger(0, maxItemCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.3.32825.248
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ItemBoxClient", "ItemBoxClient.csproj", "{D6EE7363-56EC-442E-8A50-C12111C41B59}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ItemBoxServer", "ItemBoxServer.csproj", "{35F1A00E-3387-47F2-BC89-6DB51BF829F4}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{D6EE7363-56EC-442E-8A50-C12111C41B59}.Release|x64.Build.0 = Release|x64
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{35F1A00E-3387-47F2-BC89-6DB51BF829F4}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {BE43C433-493F-4E78-9590-A780226B0FB3}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;CS0122</NoWarn>
|
||||||
|
<DefineConstants>$(DefineConstants)TRACE;CLIENT</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<NoWarn>1701;1702;CS0122</NoWarn>
|
||||||
|
<DefineConstants>$(DefineConstants)TRACE;CLIENT</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;CS0122</NoWarn>
|
||||||
|
<DefineConstants>$(DefineConstants)TRACE;CLIENT</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<NoWarn>1701;1702;CS0122</NoWarn>
|
||||||
|
<DefineConstants>$(DefineConstants)TRACE;CLIENT</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>..\Refs\0Harmony.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Barotrauma">
|
||||||
|
<HintPath>..\Refs\Client\Barotrauma.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="MonoGame.Framework.Windows.NetStandard">
|
||||||
|
<HintPath>..\Refs\MonoGame.Framework.Windows.NetStandard.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="XNATypes">
|
||||||
|
<HintPath>..\Refs\XNATypes.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Compile Remove="./Server/*.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DefineConstants>$(DefineConstants)TRACE;SERVER</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<DefineConstants>$(DefineConstants)TRACE;SERVER</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>..\Refs\0Harmony.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DedicatedServer">
|
||||||
|
<HintPath>..\Refs\Server\DedicatedServer.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="MonoGame.Framework.Windows.NetStandard">
|
||||||
|
<HintPath>..\Refs\MonoGame.Framework.Windows.NetStandard.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="NetScriptAssembly">
|
||||||
|
<HintPath>..\Refs\Server\NetScriptAssembly.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="XNATypes">
|
||||||
|
<HintPath>..\Refs\XNATypes.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Compile Remove="./Client/*.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RunConfig>
|
||||||
|
<Server>Standard</Server>
|
||||||
|
<Client>Standard</Client>
|
||||||
|
</RunConfig>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
using Barotrauma.Items.Components;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
|
||||||
|
namespace BaroMod_sjx
|
||||||
|
{
|
||||||
|
|
||||||
|
partial class ConditionStorage : ItemComponent, IServerSerializable
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
private CoroutineHandle? sendStateCoroutine;
|
||||||
|
private int lastSentState;
|
||||||
|
private float sendStateTimer;
|
||||||
|
*/
|
||||||
|
partial void OnCountPredictionChanged()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
sendStateTimer = 0.5f;
|
||||||
|
if (sendStateCoroutine == null)
|
||||||
|
{
|
||||||
|
sendStateCoroutine = CoroutineManager.StartCoroutine(SendStateAfterDelay());
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
private IEnumerable<CoroutineStatus> SendStateAfterDelay()
|
||||||
|
{
|
||||||
|
while (sendStateTimer > 0.0f)
|
||||||
|
{
|
||||||
|
sendStateTimer -= CoroutineManager.DeltaTime;
|
||||||
|
yield return CoroutineStatus.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Item.Removed || GameMain.NetworkMember == null)
|
||||||
|
{
|
||||||
|
yield return CoroutineStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStateCoroutine = null;
|
||||||
|
if (lastSentState != currentItemCount) { Item.CreateServerEvent(this); }
|
||||||
|
yield return CoroutineStatus.Success;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData? extraData = null)
|
||||||
|
{
|
||||||
|
EventData eventData = ExtractEventData<EventData>(extraData);
|
||||||
|
msg.WriteRangedInteger(eventData.ItemCount, 0, maxItemCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
using Barotrauma.Items.Components;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
|
||||||
|
namespace BaroMod_sjx
|
||||||
|
{
|
||||||
|
partial class ItemBoxImpl : ACsMod
|
||||||
|
{
|
||||||
|
const string harmony_id = "com.sjx.ItemIOFramework";
|
||||||
|
/*
|
||||||
|
const string box_identifier = "ItemBox";
|
||||||
|
const float max_condition = 1.0f;
|
||||||
|
const int item_count = 1024;
|
||||||
|
const float increment = max_condition / item_count;
|
||||||
|
*/
|
||||||
|
private readonly Harmony harmony;
|
||||||
|
|
||||||
|
public ItemBoxImpl()
|
||||||
|
{
|
||||||
|
harmony = new Harmony(harmony_id);
|
||||||
|
harmony.PatchAll(Assembly.GetExecutingAssembly());
|
||||||
|
Barotrauma.DebugConsole.AddWarning("Loaded ItemBox Impl");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Stop()
|
||||||
|
{
|
||||||
|
harmony.UnpatchAll(harmony_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static Dictionary<Type, List<ItemComponent>> get_componentsByType(Item item)
|
||||||
|
{
|
||||||
|
return (AccessTools.Field(typeof(Item), "componentsByType").GetValue(item)! as Dictionary<Type, List<ItemComponent>>)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Inventory))]
|
||||||
|
class Patch_PutItem
|
||||||
|
{
|
||||||
|
static MethodBase TargetMethod()
|
||||||
|
{
|
||||||
|
Barotrauma.DebugConsole.AddWarning("Patch_PutItem TargetMethod");
|
||||||
|
return AccessTools.Method(typeof(Inventory), "PutItem");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class context
|
||||||
|
{
|
||||||
|
public Character user;
|
||||||
|
public ConditionStorage target;
|
||||||
|
public context(Character user, ConditionStorage target)
|
||||||
|
{
|
||||||
|
this.user = user;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix(Inventory __instance, Character user, int i, out context? __state)
|
||||||
|
{
|
||||||
|
__state = null;
|
||||||
|
ConditionStorage? target = ConditionStorage.GetFromInventory(__instance);
|
||||||
|
if (target != null && i == target.slotIndex)
|
||||||
|
{
|
||||||
|
__state = new context(user, target);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public static void Postfix(context? __state)
|
||||||
|
{
|
||||||
|
if (__state != null)
|
||||||
|
{
|
||||||
|
__state.target.OnPutItemDone(__state.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Inventory), nameof(Inventory.RemoveItem))]
|
||||||
|
class Patch_RemoveItem
|
||||||
|
{
|
||||||
|
public static bool Prefix(Inventory __instance, out ConditionStorage? __state, Item item)
|
||||||
|
{
|
||||||
|
__state = null;
|
||||||
|
// do not add items if sub is unloading or if removed for overflow.
|
||||||
|
if (!Submarine.Unloading)
|
||||||
|
{
|
||||||
|
ConditionStorage? target = ConditionStorage.GetFromInventory(__instance);
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
if (target.GetSlot()?.Contains(item) ?? false)
|
||||||
|
{
|
||||||
|
if (target.flag_remove_no_spawn)
|
||||||
|
{
|
||||||
|
target.flag_remove_no_spawn = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.QualityStacked = item.Quality;
|
||||||
|
target.ConditionStacked = item.Condition;
|
||||||
|
target.item_type = item.Prefab;
|
||||||
|
__state = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public static void Postfix(ConditionStorage? __state)
|
||||||
|
{
|
||||||
|
if (__state != null)
|
||||||
|
{
|
||||||
|
__state.OnRemoveItemDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Inventory))]
|
||||||
|
class Patch_TrySwapping
|
||||||
|
{
|
||||||
|
static MethodBase TargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(Inventory), "TrySwapping");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix(Inventory __instance, Item item, ref bool __result)
|
||||||
|
{
|
||||||
|
if (ConditionStorage.GetFromInventory(__instance) != null ||
|
||||||
|
(item != null && item.ParentInventory != null && ConditionStorage.GetFromInventory(item.ParentInventory) != null))
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Inventory))]
|
||||||
|
class Patch_CreateNetworkEvent
|
||||||
|
{
|
||||||
|
static MethodBase TargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(Inventory), "CreateNetworkEvent");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix(Inventory __instance, out ConditionStorage? __state)
|
||||||
|
{
|
||||||
|
__state = null;
|
||||||
|
if (GameMain.NetworkMember != null)
|
||||||
|
{
|
||||||
|
__state = ConditionStorage.GetFromInventory(__instance);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Postfix(ConditionStorage? __state)
|
||||||
|
{
|
||||||
|
if (__state != null)
|
||||||
|
{
|
||||||
|
__state.SyncItemCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial class ConditionStorage : ItemComponent
|
||||||
|
{
|
||||||
|
private readonly struct EventData : IEventData
|
||||||
|
{
|
||||||
|
public readonly int ItemCount;
|
||||||
|
|
||||||
|
public EventData(int ItemCount)
|
||||||
|
{
|
||||||
|
this.ItemCount = ItemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serialize(0, IsPropertySaveable.No, description: "Index of the stacking slot in same item's ItemContainer component")]
|
||||||
|
public int slotIndex { get; private set; }
|
||||||
|
|
||||||
|
[Serialize(true, IsPropertySaveable.No, description: "Shows count and percentage of stacking item")]
|
||||||
|
public bool showCount { get; private set; }
|
||||||
|
|
||||||
|
[Serialize(1024, IsPropertySaveable.No, description: "Maximum number of items stacked within")]
|
||||||
|
public int maxItemCount { get; private set; }
|
||||||
|
|
||||||
|
[Serialize(true, IsPropertySaveable.No, description: "Shows icon of stacking item")]
|
||||||
|
public bool showIcon { get; private set; }
|
||||||
|
|
||||||
|
[Serialize(0.6f, IsPropertySaveable.No, description: "icon scale compared to full")]
|
||||||
|
public float iconScale { get; private set; }
|
||||||
|
|
||||||
|
[Serialize(0.0f, IsPropertySaveable.No, description: "shift x of icon")]
|
||||||
|
public float iconShiftX { get; private set; }
|
||||||
|
|
||||||
|
[Serialize(0.1f, IsPropertySaveable.No, description: "shift y of icon, down is positive")]
|
||||||
|
public float iconShiftY { get; private set; }
|
||||||
|
|
||||||
|
[Editable(minValue: 0, maxValue: int.MaxValue), Serialize(0, IsPropertySaveable.Yes, description: "Current item count")]
|
||||||
|
// camel case needed for save compatibility
|
||||||
|
public int currentItemCount
|
||||||
|
{
|
||||||
|
get => _currentItemCount;
|
||||||
|
// assume set by
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetItemCount(value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetItemCount(int value, bool is_network_event = false)
|
||||||
|
{
|
||||||
|
if (is_network_event || GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||||
|
// authoritative number. will need to send to client later if server.
|
||||||
|
{
|
||||||
|
if (value != _currentItemCount)
|
||||||
|
{
|
||||||
|
OnCountActualChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// predicted number. need to be reset later
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value != _currentItemCount)
|
||||||
|
{
|
||||||
|
OnCountPredictionChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IsActive = true;
|
||||||
|
_currentItemCount = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemInventory itemInventory => Item.OwnInventory;
|
||||||
|
public ItemContainer itemContainer => Item.GetComponent<ItemContainer>();
|
||||||
|
|
||||||
|
public int _currentItemCount;
|
||||||
|
|
||||||
|
// replace setting parent container hack, so that harpoon guns work correctly
|
||||||
|
public bool flag_remove_no_spawn;
|
||||||
|
|
||||||
|
partial void OnCountActualChanged();
|
||||||
|
partial void OnCountPredictionChanged();
|
||||||
|
|
||||||
|
|
||||||
|
[Editable, Serialize("", IsPropertySaveable.Yes, description: "current stacked item")]
|
||||||
|
public Identifier ItemIdentifier
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return item_type?.Identifier ?? "";
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.IsEmpty)
|
||||||
|
{
|
||||||
|
item_type = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item_type = ItemPrefab.Find("", value.ToIdentifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemPrefab? item_type;
|
||||||
|
|
||||||
|
[Editable(MinValueInt = 0, MaxValueInt = Quality.MaxQuality), Serialize(0, IsPropertySaveable.Yes, description: "current stacked item quality")]
|
||||||
|
public int QualityStacked { get; set; }
|
||||||
|
|
||||||
|
[Editable, Serialize(float.NaN, IsPropertySaveable.Yes, description: "current stacked item condition")]
|
||||||
|
public float ConditionStacked { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public ConditionStorage(Item item, ContentXElement element) : base(item, element) { }
|
||||||
|
|
||||||
|
public bool IsFull => currentItemCount >= maxItemCount;
|
||||||
|
public bool IsEmpty() => currentItemCount <= 0;
|
||||||
|
|
||||||
|
public void SyncItemCount()
|
||||||
|
{
|
||||||
|
#if SERVER
|
||||||
|
Item.CreateServerEvent(this, new EventData(currentItemCount));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float deltaTime, Camera cam)
|
||||||
|
{
|
||||||
|
base.Update(deltaTime, cam);
|
||||||
|
SyncItemCount();
|
||||||
|
IsActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int SlotPreserveCount(ItemPrefab prefab, Inventory inventory, ItemContainer container, int slot_index)
|
||||||
|
{
|
||||||
|
int resolved_stack_size = Math.Min(Math.Min(prefab.GetMaxStackSize(inventory), container.GetMaxStackSize(slot_index)), Inventory.MaxPossibleStackSize);
|
||||||
|
if (resolved_stack_size <= 1)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return resolved_stack_size - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConditionStorage? GetFromInventory(Inventory inventory)
|
||||||
|
{
|
||||||
|
if (inventory.Owner is Item parentItem)
|
||||||
|
{
|
||||||
|
return parentItem.GetComponent<ConditionStorage>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Inventory.ItemSlot? GetSlot()
|
||||||
|
{
|
||||||
|
Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(itemInventory)! as Inventory.ItemSlot[])!;
|
||||||
|
if (slotIndex >= slots.Length)
|
||||||
|
{
|
||||||
|
DebugConsole.LogError($"ConditionStorage of {Item.Prefab.Identifier} specified index {slotIndex} out of {slots.Length}!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return slots[slotIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPutItemDone(Character user)
|
||||||
|
{
|
||||||
|
ItemContainer container = itemContainer;
|
||||||
|
Inventory.ItemSlot target_slot;
|
||||||
|
{
|
||||||
|
Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(itemInventory)! as Inventory.ItemSlot[])!;
|
||||||
|
if (slotIndex >= slots.Length)
|
||||||
|
{
|
||||||
|
DebugConsole.LogError($"ConditionStorage of {Item.Prefab.Identifier} specified index {slotIndex} out of {slots.Length}!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target_slot = slots[slotIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target_slot.Items.Any())
|
||||||
|
{
|
||||||
|
QualityStacked = target_slot.Items.First().Quality;
|
||||||
|
ConditionStacked = target_slot.Items.First().Condition;
|
||||||
|
item_type = target_slot.Items.First().Prefab;
|
||||||
|
if (!IsFull)
|
||||||
|
{
|
||||||
|
//bool edited = false;
|
||||||
|
int preserve = SlotPreserveCount(target_slot.Items.First().Prefab, itemInventory, container, slotIndex);
|
||||||
|
var it = target_slot.Items.ToArray().AsEnumerable().GetEnumerator();
|
||||||
|
while (it.MoveNext() && !IsFull)
|
||||||
|
{
|
||||||
|
if (preserve > 0)
|
||||||
|
{
|
||||||
|
preserve--;
|
||||||
|
}
|
||||||
|
else if (Entity.Spawner != null)
|
||||||
|
{
|
||||||
|
// client cannot despawn items, single player needs to despawn
|
||||||
|
Entity.Spawner.AddItemToRemoveQueue(it.Current);
|
||||||
|
SetItemCount(currentItemCount + 1);
|
||||||
|
flag_remove_no_spawn = true;
|
||||||
|
itemInventory.RemoveItem(it.Current);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRemoveItemDone()
|
||||||
|
{
|
||||||
|
Inventory.ItemSlot target_slot;
|
||||||
|
{
|
||||||
|
Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(itemInventory)! as Inventory.ItemSlot[])!;
|
||||||
|
if (slotIndex >= slots.Length)
|
||||||
|
{
|
||||||
|
DebugConsole.LogError($"ConditionStorage of {(itemInventory.Owner as Item)!.Prefab.Identifier} specified index {slotIndex} out of {slots.Length}!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target_slot = slots[slotIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
int preserve = SlotPreserveCount(item_type!, itemInventory, itemContainer, slotIndex);
|
||||||
|
int spawn_count = preserve - target_slot.Items.Count;
|
||||||
|
int can_spawn = Math.Min(spawn_count, currentItemCount);
|
||||||
|
|
||||||
|
// other may be queued, so spawn only one
|
||||||
|
if (can_spawn > 0)
|
||||||
|
{
|
||||||
|
if (Entity.Spawner != null)
|
||||||
|
{
|
||||||
|
SetItemCount(currentItemCount - 1);
|
||||||
|
|
||||||
|
Item.Spawner.AddItemToSpawnQueue(item_type, itemInventory,
|
||||||
|
ConditionStacked, QualityStacked, spawnIfInventoryFull: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Item identifier="StackBox" category="Equipment" tags="smallitem,io_box,tool" health="1" scale="0.5" impactsoundtag="impact_metal_heavy" showcontentsintooltip="true" canflipx="false" waterproof="true" fireproof="true" Indestructible="true">
|
||||||
|
<Fabricate suitablefabricators="fabricator" outcondition="0" requiredtime="20">
|
||||||
|
<RequiredSkill identifier="mechanical" level="20" />
|
||||||
|
<RequiredItem identifier="steel" amount="2" />
|
||||||
|
</Fabricate>
|
||||||
|
<Fabricate suitablefabricators="vendingmachine" requiredtime="1" requiredmoney="10000" fabricationlimitmin="10" fabricationlimitmax="1000" quality="0" outcondition="0" />
|
||||||
|
<InventoryIcon texture="Content/Items/InventoryIconAtlas.png" sourcerect="640,256,64,64" origin="0.5,0.6" />
|
||||||
|
<Sprite texture="Content/Items/Tools/tools.png" sourcerect="314,1,94,74" origin="0.5,0.5" depth="0.6" />
|
||||||
|
<Body width="90" height="60" density="20" />
|
||||||
|
<ItemContainer capacity="1" AllowSwappingContainedItems="false" />
|
||||||
|
<ConditionStorage maxItemCount="1024" slotIndex="0" iconScale="0.6" iconShiftX="0" iconShiftY="0.1" />
|
||||||
|
<Holdable slots="Any,RightHand,LeftHand" holdpos="0,-70" handle1="-5,0" handle2="10,-20" holdangle="0" msg="ItemMsgPickUpUse" canbeselected="false" canbepicked="true" pickkey="Use" allowswappingwhenpicked="false" />
|
||||||
|
</Item>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<infotexts language="English" nowhitespace="false" translatedname="English">
|
||||||
|
<entityname.StackBox>Item Box</entityname.StackBox>
|
||||||
|
<entitydescription.StackBox>Stack your stackable items in one entity.</entitydescription.StackBox>
|
||||||
|
</infotexts>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<infotexts language="Simplified Chinese" nowhitespace="false" translatedname="中文(简体)">
|
||||||
|
<entityname.StackBox>物品箱</entityname.StackBox>
|
||||||
|
<entitydescription.StackBox>让你可叠加的物品叠加在一个实体上面</entitydescription.StackBox>
|
||||||
|
</infotexts>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="Item IO Framework" steamworkshopid="2950383008" corepackage="false" modversion="0.0.19" gameversion="1.7.7.0" installtime="1735909045" expectedhash="F6436554A34A3425D3C68FBF9DDB4FBA">
|
||||||
|
<Item file="%ModDir%/XML/ItemBox.xml" />
|
||||||
|
<Text file="%ModDir%/XML/Text/English.xml" />
|
||||||
|
<Text file="%ModDir%/XML/Text/SimplifiedChinese.xml" />
|
||||||
|
</contentpackage>
|
||||||
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3389755246/ExtinguisherComponent.Png
(Stored with Git LFS)
Normal file
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3389755246/ExtinguisherComponent.Png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 916 KiB |
Binary file not shown.
@@ -0,0 +1,306 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Items>
|
||||||
|
<Item name="Extinguisher Component" identifier="ExtinguisherComponent" category="Equipment" Tags="smallitem,tool,signal" cargocontaineridentifier="metalcrate" requireaimtouse="true" characterusable="false" Scale="0.65" impactsoundtag="impact_metal_light" maxstacksize="2">
|
||||||
|
<PreferredContainer primary="engcab" />
|
||||||
|
<PreferredContainer primary="wreckstoragecab,abandonedstoragecab,piratestoragecab" amount="1" spawnprobability="0.0125" />
|
||||||
|
<PreferredContainer primary="outpostcrewcabinet" amount="1" spawnprobability="0.025" />
|
||||||
|
<Deconstruct time="10">
|
||||||
|
<Item identifier="Copper" amount="2" />
|
||||||
|
</Deconstruct>
|
||||||
|
<Fabricate suitablefabricators="fabricator" requiredtime="20">
|
||||||
|
<Item identifier="Copper" amount="2" />
|
||||||
|
</Fabricate>
|
||||||
|
<Price baseprice="44">
|
||||||
|
<Price storeidentifier="merchantoutpost" minavailable="2" />
|
||||||
|
<Price storeidentifier="merchantcity" minavailable="3" />
|
||||||
|
<Price storeidentifier="merchantresearch" multiplier="1.25" minavailable="1" />
|
||||||
|
<Price storeidentifier="merchantmilitary" multiplier="1.25" minavailable="1" />
|
||||||
|
<Price storeidentifier="merchantmine" multiplier="1.25" minavailable="4" />
|
||||||
|
<Price storeidentifier="merchantengineering" minavailable="2" multiplier="0.9" />
|
||||||
|
</Price>
|
||||||
|
<Sprite texture="%ModDir%/ExtinguisherComponent.Png" sourcerect="0,0,92,52" depth="0.55" canflipx="true" origin="0.5,0.5" />
|
||||||
|
<Body width="92" height="52" density="50" />
|
||||||
|
<Holdable selectkey="Select" pickkey="Use" slots="Any,RightHand+LeftHand" msg="ItemMsgDetachWrench" PickingTime="10.0" aimpos="65,-10" handle1="0,0" attachable="true" aimable="false">
|
||||||
|
<RequiredItem identifier="wrench" type="Equipped" />
|
||||||
|
</Holdable>
|
||||||
|
<RepairTool wateramount="0.0" extinguishamount="50.0" range="600" barrelpos="0,-26" barrelrotation="270" spread="30" unskilledspread="0" targetstructures="false" hititems="false" characterusable="false" requireaimtouse="false" usablein="air">
|
||||||
|
<ParticleEmitter particle="extinguisher" velocitymin="1000.0" velocitymax="1650.0" particlespersecond="60" anglemin="-10" anglemax="10" />
|
||||||
|
<sound file="Content/Items/Tools/Extinguisher.ogg" type="OnUse" range="500.0" loop="true" />
|
||||||
|
<StatusEffect type="OnNotContained" target="This" setvalue="true" requireaimtouse="false" />
|
||||||
|
<StatusEffect type="OnContained" target="This" setvalue="true" requireaimtouse="true" />
|
||||||
|
</RepairTool>
|
||||||
|
<RepairTool wateramount="0.0" extinguishamount="50.0" range="600" barrelpos="-31,-24" barrelrotation="230" spread="30" unskilledspread="0" targetstructures="false" hititems="false" characterusable="false" requireaimtouse="false" usablein="air">
|
||||||
|
<ParticleEmitter particle="extinguisher" velocitymin="1000.0" velocitymax="1650.0" particlespersecond="60" anglemin="-10" anglemax="10" />
|
||||||
|
<sound file="Content/Items/Tools/Extinguisher.ogg" type="OnUse" range="500.0" loop="true" />
|
||||||
|
<StatusEffect type="OnNotContained" target="This" setvalue="true" requireaimtouse="false" />
|
||||||
|
<StatusEffect type="OnContained" target="This" setvalue="true" requireaimtouse="true" />
|
||||||
|
</RepairTool>
|
||||||
|
<RepairTool wateramount="0.0" extinguishamount="50.0" range="600" barrelpos="31,-24" barrelrotation="310" spread="30" unskilledspread="0" targetstructures="false" hititems="false" characterusable="false" requireaimtouse="false" usablein="air">
|
||||||
|
<ParticleEmitter particle="extinguisher" velocitymin="1000.0" velocitymax="1650.0" particlespersecond="60" anglemin="-10" anglemax="10" />
|
||||||
|
<sound file="Content/Items/Tools/Extinguisher.ogg" type="OnUse" range="500.0" loop="true" />
|
||||||
|
<StatusEffect type="OnNotContained" target="This" setvalue="true" requireaimtouse="false" />
|
||||||
|
<StatusEffect type="OnContained" target="This" setvalue="true" requireaimtouse="true" />
|
||||||
|
</RepairTool>
|
||||||
|
<LightComponent lightcolor="0,0,0,0" range="0.1" powerconsumption="1" IsOn="true" castshadows="false" allowingameediting="false">
|
||||||
|
<StatusEffect type="OnActive" target="This">
|
||||||
|
<RequiredItems items="extinguisher" type="Contained" />
|
||||||
|
<UseItem />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnActive" target="Contained" condition="-6">
|
||||||
|
<RequiredItems items="extinguisher" type="Contained" />
|
||||||
|
</StatusEffect>
|
||||||
|
</LightComponent>
|
||||||
|
<ConnectionPanel selectkey="Action" canbeselected="true" msg="ItemMsgRewireScrewdriver" hudpriority="10">
|
||||||
|
<GuiFrame relativesize="0.2,0.32" minsize="400,350" maxsize="480,420" anchor="Center" style="ConnectionPanel" />
|
||||||
|
<RequiredItem items="screwdriver" type="Equipped" />
|
||||||
|
<input name="power" displayname="connection.power" />
|
||||||
|
<input name="toggle" displayname="connection.togglestate" />
|
||||||
|
<input name="set_state" displayname="connection.setstate" />
|
||||||
|
</ConnectionPanel>
|
||||||
|
<ItemContainer hideitems="false" containedspritedepth="0.56" drawinventory="true" capacity="1" AutoInteractWithContained="false" itempos="22.5,-16" itemrotation="270" canbeselected="true" msg="ItemMsgInteractSelect">
|
||||||
|
<GuiFrame relativesize="0.2,0.25" anchor="Center" minsize="140,170" maxsize="280,280" style="ItemUI" />
|
||||||
|
<Containable items="extinguisher" />
|
||||||
|
</ItemContainer>
|
||||||
|
</Item>
|
||||||
|
<Item name="Flamer Component" identifier="FlamerComponent" category="Equipment" Tags="smallitem,tool,signal" cargocontaineridentifier="metalcrate" fireproof="true" requireaimtouse="true" characterusable="false" Scale="0.45" impactsoundtag="impact_metal_light" maxstacksize="2">
|
||||||
|
<PreferredContainer primary="engcab" />
|
||||||
|
<PreferredContainer primary="wreckstoragecab,abandonedstoragecab,piratestoragecab" amount="1" spawnprobability="0.0125" />
|
||||||
|
<PreferredContainer primary="outpostcrewcabinet" amount="1" spawnprobability="0.025" />
|
||||||
|
<Deconstruct time="10">
|
||||||
|
<Item identifier="Copper" amount="2" />
|
||||||
|
</Deconstruct>
|
||||||
|
<Fabricate suitablefabricators="fabricator" requiredtime="20">
|
||||||
|
<Item identifier="Copper" amount="2" />
|
||||||
|
</Fabricate>
|
||||||
|
<Price baseprice="44">
|
||||||
|
<Price storeidentifier="merchantoutpost" minavailable="2" />
|
||||||
|
<Price storeidentifier="merchantcity" minavailable="3" />
|
||||||
|
<Price storeidentifier="merchantresearch" multiplier="1.25" minavailable="1" />
|
||||||
|
<Price storeidentifier="merchantmilitary" multiplier="1.25" minavailable="1" />
|
||||||
|
<Price storeidentifier="merchantmine" multiplier="1.25" minavailable="4" />
|
||||||
|
<Price storeidentifier="merchantengineering" minavailable="2" multiplier="0.9" />
|
||||||
|
</Price>
|
||||||
|
<Sprite texture="%ModDir%/FlamerComponent.Png" sourcerect="0,0,92,52" depth="0.55" canflipx="true" origin="0.5,0.5" />
|
||||||
|
<Body width="92" height="52" density="50" />
|
||||||
|
<Holdable selectkey="Select" pickkey="Use" slots="Any,RightHand+LeftHand" msg="ItemMsgDetachWrench" PickingTime="10.0" aimpos="65,-10" handle1="0,0" attachable="true" aimable="false">
|
||||||
|
<RequiredItem identifier="wrench" type="Equipped" />
|
||||||
|
</Holdable>
|
||||||
|
<RepairTool firedamage="25.0" structurefixamount="0.0" usablein="Air" range="600" barrelpos="0,-26" barrelrotation="270" fireprobability="0.1" repairmultiple="true" RepairMultipleWalls="false" repairthroughwalls="false" repairthroughholes="true" combatpriority="50" spread="25" unskilledspread="25">
|
||||||
|
<!-- the item must contain a welding fuel tank for it to work -->
|
||||||
|
<StatusEffect type="OnNotContained" target="This" setvalue="true" requireaimtouse="false" />
|
||||||
|
<StatusEffect type="OnContained" target="This" setvalue="true" requireaimtouse="true" />
|
||||||
|
<ParticleEmitter particle="flamethrower" particlespersecond="80" anglemin="0" anglemax="0" velocitymin="1000" velocitymax="1500" highqualitycollisiondetection="true" />
|
||||||
|
<ParticleEmitter particle="flamethrowersmoke" particlespersecond="80" anglemin="0" anglemax="0" velocitymin="500" velocitymax="1000" />
|
||||||
|
<sound file="Content/Items/Weapons/FlameThrowerLoop.ogg" type="OnUse" range="750.0" loop="true" />
|
||||||
|
<StatusEffect type="OnFailure" target="This">
|
||||||
|
<ParticleEmitter particle="bubbles" particlespersecond="20" anglemin="-10" anglemax="10" scalemin="0.3" scalemax="0.7" velocitymin="5" velocitymax="100" copyentityangle="true" />
|
||||||
|
<ParticleEmitter particle="fleshsmoke" particlespersecond="10" anglemin="-10" anglemax="10" scalemin="1" scalemax="1.5" velocitymin="5" velocitymax="200" copyentityangle="true" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnSuccess" targettype="UseTarget" targets="item" Condition="-15.0">
|
||||||
|
<Conditional HasTag="neq weldable" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- make the item unusable when there's less than 10% oxygen -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull,This" UsableIn="None">
|
||||||
|
<Conditional OxygenPercentage="lt 10" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- make the item usable again when there's more than 10% oxygen -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull,This" UsableIn="Air">
|
||||||
|
<Conditional OxygenPercentage="gt 10" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- when using, the contained welding fuel tank will detoriate (= lose fuel) -->
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="weldingfueltank" Condition="-25.0" />
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="incendiumfueltank" Condition="-10.0" />
|
||||||
|
<!-- do burn damage to characters -->
|
||||||
|
<StatusEffect type="OnUse" target="UseTarget">
|
||||||
|
<Conditional InWater="false" />
|
||||||
|
<Affliction identifier="burn" amount="1.25" />
|
||||||
|
<Affliction identifier="burning" amount="2" dividebylimbcount="true" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" target="UseTarget">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
<Conditional InWater="false" />
|
||||||
|
<Affliction identifier="burn" amount="2.5" />
|
||||||
|
<Affliction identifier="burning" amount="2" dividebylimbcount="true" />
|
||||||
|
<Affliction identifier="stun" strength="1" probability="0.1" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" target="This" firedamage="40.0" fireprobability="0.35" setvalue="true">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- explode if oxygen tanks are inserted -->
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="oxygentank" delay="1.0" stackable="false" Condition="0" setvalue="true">
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall1.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall2.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall3.ogg" range="2000" />
|
||||||
|
<Explosion range="150.0" force="3" applyfireeffects="false">
|
||||||
|
<Affliction identifier="burn" strength="25" />
|
||||||
|
<Affliction identifier="stun" strength="5" />
|
||||||
|
</Explosion>
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="oxygenitetank" delay="1.0" stackable="false" Condition="0" setvalue="true">
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall1.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall2.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall3.ogg" range="2000" />
|
||||||
|
<Explosion range="150.0" force="6" applyfireeffects="false">
|
||||||
|
<Affliction identifier="burn" strength="50" />
|
||||||
|
<Affliction identifier="stun" strength="10" />
|
||||||
|
</Explosion>
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- consume oxygen from the hull -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull" Oxygen="-10000" />
|
||||||
|
</RepairTool>
|
||||||
|
<RepairTool firedamage="25.0" structurefixamount="0.0" usablein="Air" range="600" barrelpos="-31,-24" barrelrotation="230" fireprobability="0.1" repairmultiple="true" RepairMultipleWalls="false" repairthroughwalls="false" repairthroughholes="true" combatpriority="50" spread="25" unskilledspread="25">
|
||||||
|
<!-- the item must contain a welding fuel tank for it to work -->
|
||||||
|
<StatusEffect type="OnNotContained" target="This" setvalue="true" requireaimtouse="false" />
|
||||||
|
<StatusEffect type="OnContained" target="This" setvalue="true" requireaimtouse="true" />
|
||||||
|
<ParticleEmitter particle="flamethrower" particlespersecond="80" anglemin="0" anglemax="0" velocitymin="1000" velocitymax="1500" highqualitycollisiondetection="true" />
|
||||||
|
<ParticleEmitter particle="flamethrowersmoke" particlespersecond="80" anglemin="0" anglemax="0" velocitymin="500" velocitymax="1000" />
|
||||||
|
<sound file="Content/Items/Weapons/FlameThrowerLoop.ogg" type="OnUse" range="750.0" loop="true" />
|
||||||
|
<StatusEffect type="OnFailure" target="This">
|
||||||
|
<ParticleEmitter particle="bubbles" particlespersecond="20" anglemin="-10" anglemax="10" scalemin="0.3" scalemax="0.7" velocitymin="5" velocitymax="100" copyentityangle="true" />
|
||||||
|
<ParticleEmitter particle="fleshsmoke" particlespersecond="10" anglemin="-10" anglemax="10" scalemin="1" scalemax="1.5" velocitymin="5" velocitymax="200" copyentityangle="true" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnSuccess" targettype="UseTarget" targets="item" Condition="-15.0">
|
||||||
|
<Conditional HasTag="neq weldable" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- make the item unusable when there's less than 10% oxygen -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull,This" UsableIn="None">
|
||||||
|
<Conditional OxygenPercentage="lt 10" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- make the item usable again when there's more than 10% oxygen -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull,This" UsableIn="Air">
|
||||||
|
<Conditional OxygenPercentage="gt 10" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- when using, the contained welding fuel tank will detoriate (= lose fuel) -->
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="weldingfueltank" Condition="-25.0" />
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="incendiumfueltank" Condition="-10.0" />
|
||||||
|
<!-- do burn damage to characters -->
|
||||||
|
<StatusEffect type="OnUse" target="UseTarget">
|
||||||
|
<Conditional InWater="false" />
|
||||||
|
<Affliction identifier="burn" amount="1.25" />
|
||||||
|
<Affliction identifier="burning" amount="2" dividebylimbcount="true" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" target="UseTarget">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
<Conditional InWater="false" />
|
||||||
|
<Affliction identifier="burn" amount="2.5" />
|
||||||
|
<Affliction identifier="burning" amount="2" dividebylimbcount="true" />
|
||||||
|
<Affliction identifier="stun" strength="1" probability="0.1" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" target="This" firedamage="40.0" fireprobability="0.35" setvalue="true">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- explode if oxygen tanks are inserted -->
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="oxygentank" delay="1.0" stackable="false" Condition="0" setvalue="true">
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall1.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall2.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall3.ogg" range="2000" />
|
||||||
|
<Explosion range="150.0" force="3" applyfireeffects="false">
|
||||||
|
<Affliction identifier="burn" strength="25" />
|
||||||
|
<Affliction identifier="stun" strength="5" />
|
||||||
|
</Explosion>
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="oxygenitetank" delay="1.0" stackable="false" Condition="0" setvalue="true">
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall1.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall2.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall3.ogg" range="2000" />
|
||||||
|
<Explosion range="150.0" force="6" applyfireeffects="false">
|
||||||
|
<Affliction identifier="burn" strength="50" />
|
||||||
|
<Affliction identifier="stun" strength="10" />
|
||||||
|
</Explosion>
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- consume oxygen from the hull -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull" Oxygen="-10000" />
|
||||||
|
</RepairTool>
|
||||||
|
<RepairTool firedamage="25.0" structurefixamount="0.0" usablein="Air" range="600" barrelpos="31,-24" barrelrotation="310" fireprobability="0.1" repairmultiple="true" RepairMultipleWalls="false" repairthroughwalls="false" repairthroughholes="true" combatpriority="50" spread="25" unskilledspread="25">
|
||||||
|
<!-- the item must contain a welding fuel tank for it to work -->
|
||||||
|
<StatusEffect type="OnNotContained" target="This" setvalue="true" requireaimtouse="false" />
|
||||||
|
<StatusEffect type="OnContained" target="This" setvalue="true" requireaimtouse="true" />
|
||||||
|
<ParticleEmitter particle="flamethrower" particlespersecond="80" anglemin="0" anglemax="0" velocitymin="1000" velocitymax="1500" highqualitycollisiondetection="true" />
|
||||||
|
<ParticleEmitter particle="flamethrowersmoke" particlespersecond="80" anglemin="0" anglemax="0" velocitymin="500" velocitymax="1000" />
|
||||||
|
<sound file="Content/Items/Weapons/FlameThrowerLoop.ogg" type="OnUse" range="750.0" loop="true" />
|
||||||
|
<StatusEffect type="OnFailure" target="This">
|
||||||
|
<ParticleEmitter particle="bubbles" particlespersecond="20" anglemin="-10" anglemax="10" scalemin="0.3" scalemax="0.7" velocitymin="5" velocitymax="100" copyentityangle="true" />
|
||||||
|
<ParticleEmitter particle="fleshsmoke" particlespersecond="10" anglemin="-10" anglemax="10" scalemin="1" scalemax="1.5" velocitymin="5" velocitymax="200" copyentityangle="true" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnSuccess" targettype="UseTarget" targets="item" Condition="-15.0">
|
||||||
|
<Conditional HasTag="neq weldable" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- make the item unusable when there's less than 10% oxygen -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull,This" UsableIn="None">
|
||||||
|
<Conditional OxygenPercentage="lt 10" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- make the item usable again when there's more than 10% oxygen -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull,This" UsableIn="Air">
|
||||||
|
<Conditional OxygenPercentage="gt 10" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- when using, the contained welding fuel tank will detoriate (= lose fuel) -->
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="weldingfueltank" Condition="-25.0" />
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="incendiumfueltank" Condition="-10.0" />
|
||||||
|
<!-- do burn damage to characters -->
|
||||||
|
<StatusEffect type="OnUse" target="UseTarget">
|
||||||
|
<Conditional InWater="false" />
|
||||||
|
<Affliction identifier="burn" amount="1.25" />
|
||||||
|
<Affliction identifier="burning" amount="2" dividebylimbcount="true" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" target="UseTarget">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
<Conditional InWater="false" />
|
||||||
|
<Affliction identifier="burn" amount="2.5" />
|
||||||
|
<Affliction identifier="burning" amount="2" dividebylimbcount="true" />
|
||||||
|
<Affliction identifier="stun" strength="1" probability="0.1" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" target="This" firedamage="40.0" fireprobability="0.35" setvalue="true">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- explode if oxygen tanks are inserted -->
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="oxygentank" delay="1.0" stackable="false" Condition="0" setvalue="true">
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall1.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall2.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall3.ogg" range="2000" />
|
||||||
|
<Explosion range="150.0" force="3" applyfireeffects="false">
|
||||||
|
<Affliction identifier="burn" strength="25" />
|
||||||
|
<Affliction identifier="stun" strength="5" />
|
||||||
|
</Explosion>
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnUse" targettype="Contained" targets="oxygenitetank" delay="1.0" stackable="false" Condition="0" setvalue="true">
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall1.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall2.ogg" range="2000" />
|
||||||
|
<sound file="Content/Items/Weapons/ExplosionSmall3.ogg" range="2000" />
|
||||||
|
<Explosion range="150.0" force="6" applyfireeffects="false">
|
||||||
|
<Affliction identifier="burn" strength="50" />
|
||||||
|
<Affliction identifier="stun" strength="10" />
|
||||||
|
</Explosion>
|
||||||
|
</StatusEffect>
|
||||||
|
<!-- consume oxygen from the hull -->
|
||||||
|
<StatusEffect type="OnUse" target="Hull" Oxygen="-10000" />
|
||||||
|
</RepairTool>
|
||||||
|
<LightComponent lightcolor="0,0,0,0" range="0.1" powerconsumption="1" IsOn="true" castshadows="false" allowingameediting="false">
|
||||||
|
<StatusEffect type="OnActive" target="This">
|
||||||
|
<RequiredItem items="weldingfueltank" type="Contained" />
|
||||||
|
<UseItem />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnActive" target="This">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
<UseItem />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnActive" target="Contained" targets="weldingfueltank" Condition="-25.0">
|
||||||
|
<RequiredItem items="weldingfueltank" type="Contained" />
|
||||||
|
</StatusEffect>
|
||||||
|
<StatusEffect type="OnActive" target="Contained" targets="incendiumfueltank" Condition="-10.0">
|
||||||
|
<RequiredItem items="incendiumfueltank" type="Contained" />
|
||||||
|
</StatusEffect>
|
||||||
|
</LightComponent>
|
||||||
|
<ConnectionPanel selectkey="Action" canbeselected="true" msg="ItemMsgRewireScrewdriver" hudpriority="10">
|
||||||
|
<GuiFrame relativesize="0.2,0.32" minsize="400,350" maxsize="480,420" anchor="Center" style="ConnectionPanel" />
|
||||||
|
<RequiredItem items="screwdriver" type="Equipped" />
|
||||||
|
<input name="power" displayname="connection.power" />
|
||||||
|
<input name="toggle" displayname="connection.togglestate" />
|
||||||
|
<input name="set_state" displayname="connection.setstate" />
|
||||||
|
</ConnectionPanel>
|
||||||
|
<ItemContainer hideitems="false" containedspritedepth="0.56" drawinventory="true" capacity="1" AutoInteractWithContained="false" itempos="22.5,-16" itemrotation="270" canbeselected="true" msg="ItemMsgInteractSelect">
|
||||||
|
<GuiFrame relativesize="0.2,0.25" anchor="Center" minsize="140,170" maxsize="280,280" style="ItemUI" />
|
||||||
|
<SlotIcon slotindex="0" texture="Content/UI/StatusMonitorUI.png" sourcerect="64,448,64,64" origin="0.5,0.5" />
|
||||||
|
<Containable items="weldingtoolfuel,oxygensource" />
|
||||||
|
</ItemContainer>
|
||||||
|
</Item>
|
||||||
|
</Items>
|
||||||
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3389755246/FlamerComponent.Png
(Stored with Git LFS)
Normal file
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3389755246/FlamerComponent.Png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="ExtinguisherComponent" steamworkshopid="3389755246" corepackage="false" modversion="1.0.1" gameversion="1.7.7.0" installtime="1734967548" altnames="ExtinguisherComponent" expectedhash="A18E147B0228CB082A43503485D12761">
|
||||||
|
<Item file="%ModDir%/ExtinguisherComponent.xml" />
|
||||||
|
</contentpackage>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Barotrauma;
|
||||||
|
using Barotrauma.Items.Components;
|
||||||
|
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BmsUtils
|
||||||
|
{
|
||||||
|
public static class Util
|
||||||
|
{
|
||||||
|
private static List<int> GetStackBoxIndex(ItemInventory inv)
|
||||||
|
{
|
||||||
|
var stackBoxsIndex = new List<int>();
|
||||||
|
for (var i = 0; i < inv.Capacity; i++)
|
||||||
|
{
|
||||||
|
var items = inv.GetItemsAt(i).ToList();
|
||||||
|
if (items.None()) { continue; }
|
||||||
|
if (items.First().Prefab.Identifier.ToString() == "StackBox" && items.First().OwnInventories.First().AllItemsMod.Any())
|
||||||
|
{
|
||||||
|
stackBoxsIndex.Add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stackBoxsIndex;
|
||||||
|
}
|
||||||
|
public static void PushItems(bool toStackBox = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
var controlCharacter = Character.Controlled;
|
||||||
|
var selectedContainer = controlCharacter.SelectedItem?.GetComponent<ItemContainer>();
|
||||||
|
var leftHandItems = controlCharacter.Inventory.GetItemsAt(5).FirstOrDefault()?.OwnInventory;
|
||||||
|
var rightHandItems = controlCharacter.Inventory.GetItemsAt(6).FirstOrDefault()?.OwnInventory;
|
||||||
|
|
||||||
|
if (leftHandItems != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < leftHandItems.Capacity; i++)
|
||||||
|
{
|
||||||
|
foreach (var item in leftHandItems.GetItemsAt(i).ToList())
|
||||||
|
{
|
||||||
|
if (toStackBox)
|
||||||
|
{
|
||||||
|
foreach (var boxIndex in GetStackBoxIndex(selectedContainer.Inventory))
|
||||||
|
{
|
||||||
|
selectedContainer.Inventory.TryPutItem(item, boxIndex, allowSwapping: false, allowCombine: true, user: null, createNetworkEvent: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectedContainer.Inventory.TryPutItem(item, controlCharacter, createNetworkEvent: true, ignoreCondition: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rightHandItems != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < rightHandItems.Capacity; i++)
|
||||||
|
{
|
||||||
|
foreach (var item in rightHandItems.GetItemsAt(i).ToList())
|
||||||
|
{
|
||||||
|
if (toStackBox)
|
||||||
|
{
|
||||||
|
foreach (var boxIndex in GetStackBoxIndex(selectedContainer.Inventory))
|
||||||
|
{
|
||||||
|
selectedContainer.Inventory.TryPutItem(item, boxIndex, allowSwapping: false, allowCombine: true, user: null, createNetworkEvent: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectedContainer.Inventory.TryPutItem(item, controlCharacter, createNetworkEvent: true, ignoreCondition: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Linq;
|
||||||
|
using Barotrauma;
|
||||||
|
using BmsUtils;
|
||||||
|
|
||||||
|
// debug
|
||||||
|
// using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Bms_Harmony
|
||||||
|
{
|
||||||
|
partial class BmsHarmony : ACsMod
|
||||||
|
{
|
||||||
|
const string harmony_id = "com.Bms.Harmony";
|
||||||
|
private readonly Harmony harmony;
|
||||||
|
|
||||||
|
public override void Stop()
|
||||||
|
{
|
||||||
|
harmony.UnpatchAll(harmony_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BmsHarmony()
|
||||||
|
{
|
||||||
|
harmony = new Harmony(harmony_id);
|
||||||
|
harmony.PatchAll(Assembly.GetExecutingAssembly());
|
||||||
|
Barotrauma.DebugConsole.AddWarning("Loaded BmsHarmony");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Barotrauma.Items.Components.ItemContainer))]
|
||||||
|
class Patch_MergeStacks
|
||||||
|
{
|
||||||
|
static MethodBase TargetMethod()
|
||||||
|
{
|
||||||
|
Barotrauma.DebugConsole.AddWarning("Patch_MergeStacks TargetMethod");
|
||||||
|
return AccessTools.Method(typeof(Barotrauma.Items.Components.ItemContainer), "MergeStacks");
|
||||||
|
}
|
||||||
|
static bool Prefix(Barotrauma.Items.Components.ItemContainer __instance)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < __instance.Inventory.Capacity - 1; i++)
|
||||||
|
{
|
||||||
|
var items = __instance.Inventory.GetItemsAt(i).ToList();
|
||||||
|
if (items.None()) { continue; }
|
||||||
|
if (items.First().Prefab.Identifier.ToString() == "StackBox" &&
|
||||||
|
items.First().OwnInventories.First().AllItemsMod.Any())
|
||||||
|
{
|
||||||
|
for (int j = 0; j < __instance.Inventory.Capacity - 1; j++)
|
||||||
|
{
|
||||||
|
var items2 = __instance.Inventory.GetItemsAt(j).ToList();
|
||||||
|
if (items2.None()) { continue; }
|
||||||
|
if (items2.First().Prefab.Identifier.ToString() != "StackBox")
|
||||||
|
{
|
||||||
|
items2.ForEach(it => __instance.Inventory.TryPutItem(it, i, allowSwapping: false, allowCombine: true, user: null, createNetworkEvent: false));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Barotrauma.Items.Components.ItemContainer))]
|
||||||
|
class Patch_CreateGUI
|
||||||
|
{
|
||||||
|
static MethodBase TargetMethod()
|
||||||
|
{
|
||||||
|
Barotrauma.DebugConsole.AddWarning("Patch_CreateGUI TargetMethod");
|
||||||
|
return AccessTools.Method(typeof(Barotrauma.Items.Components.ItemContainer), "CreateGUI");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Postfix(Barotrauma.Items.Components.ItemContainer __instance)
|
||||||
|
{
|
||||||
|
if (__instance.Inventory.Capacity > 1)
|
||||||
|
{
|
||||||
|
var layoutGroup = __instance.GuiFrame.FindChild(c => c is Barotrauma.GUILayoutGroup, recursive: true);
|
||||||
|
new GUIButton(new RectTransform(Vector2.One, layoutGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "PushButton")
|
||||||
|
{
|
||||||
|
ToolTip = TextManager.Get("bms.pushicon"),
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
Util.PushItems(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RunConfig>
|
||||||
|
<Server>Standard</Server>
|
||||||
|
<Client>Standard</Client>
|
||||||
|
</RunConfig>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Diemoe
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<infotexts language="English" nowhitespace="false" translatedname="English">
|
||||||
|
<bms.pushicon>
|
||||||
|
All transferred to item box
|
||||||
|
</bms.pushicon>
|
||||||
|
</infotexts>
|
||||||
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3406279065/Text/Push.png
(Stored with Git LFS)
Normal file
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3406279065/Text/Push.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<infotexts language="Simplified Chinese" nowhitespace="false" translatedname="中文(简体)">
|
||||||
|
<bms.pushicon>
|
||||||
|
全部转移至物品箱
|
||||||
|
</bms.pushicon>
|
||||||
|
</infotexts>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<style>
|
||||||
|
<PushButton color="169,212,187,255" hovercolor="220,220,220,255" selectedcolor="255,255,255,255" pressedcolor="100,100,100,255" disabledcolor="125,125,125,125">
|
||||||
|
<Sprite name="PushButton" texture="%ModDir%/Text/Push.png" sourcerect="0,0,32,32" tile="false" maintainaspectratio="true" origin="0.5,0.5" />
|
||||||
|
</PushButton>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="ItemIO BetterMergeStack" steamworkshopid="3406279065" corepackage="false" modversion="1.0.2" gameversion="1.7.7.0" installtime="1737277942" expectedhash="98D1C5B0293A1539E0A330951231E576">
|
||||||
|
<Other file="%ModDir%/LICENSE" />
|
||||||
|
<UIStyle file="%ModDir%/Text/style.xml" />
|
||||||
|
<Text file="%ModDir%/Text/English.xml" />
|
||||||
|
<Text file="%ModDir%/Text/SimplifiedChinese.xml" />
|
||||||
|
</contentpackage>
|
||||||
@@ -0,0 +1,466 @@
|
|||||||
|
if SERVER then return end
|
||||||
|
|
||||||
|
LuaUserData.RegisterType("Barotrauma.Items.Components.ItemContainer+SlotRestrictions")
|
||||||
|
LuaUserData.RegisterType('System.Collections.Immutable.ImmutableArray`1[[Barotrauma.Items.Components.ItemContainer+SlotRestrictions, Barotrauma]]')
|
||||||
|
LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.Items.Components.ItemContainer'], 'slotRestrictions')
|
||||||
|
LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.ItemInventory'], 'slots')
|
||||||
|
LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterInventory"], "slots")
|
||||||
|
|
||||||
|
-- 配置重试参数
|
||||||
|
local RETRY_CONFIG = {
|
||||||
|
INTERVAL = 0.3, -- 重试间隔(秒)
|
||||||
|
MAX_ATTEMPTS = 3, -- 最大尝试次数
|
||||||
|
VALIDITY_DURATION = 5,-- 记录有效期(秒)
|
||||||
|
GENERATION_INTERVAL = 0.5 -- 分代时间间隔
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 状态存储表
|
||||||
|
local retryQueue = {} -- 结构:
|
||||||
|
-- {
|
||||||
|
-- [magID] =
|
||||||
|
-- {
|
||||||
|
-- generations =
|
||||||
|
-- {
|
||||||
|
-- [generationID] = { attempts, nextAttempt, expiry },
|
||||||
|
-- ...
|
||||||
|
-- },
|
||||||
|
-- mag = entityRef
|
||||||
|
-- },
|
||||||
|
-- ...
|
||||||
|
-- }
|
||||||
|
|
||||||
|
local function isSlotFull(slotRestriction, slot)
|
||||||
|
return slotRestriction.MaxStackSize - #slot.items
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tryPutItemsInInventory(character, hand, anotherhand, handInv, handIEnumerable, anotherhandIEnumerable)
|
||||||
|
|
||||||
|
-- 重试队列管理
|
||||||
|
local function addToRetryQueue(mag)
|
||||||
|
if not mag then return end
|
||||||
|
|
||||||
|
local currentTime = Timer.GetTime()
|
||||||
|
local magID = mag.ID
|
||||||
|
|
||||||
|
-- 生成分代ID(每0.5秒为一个分代)
|
||||||
|
local generationID = math.floor(currentTime / RETRY_CONFIG.GENERATION_INTERVAL)
|
||||||
|
|
||||||
|
-- 初始化队列条目
|
||||||
|
if not retryQueue[magID] then
|
||||||
|
retryQueue[magID] = {
|
||||||
|
mag = mag,
|
||||||
|
generations = {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 更新分代记录
|
||||||
|
local entry = retryQueue[magID]
|
||||||
|
if not entry.generations[generationID] then
|
||||||
|
entry.generations[generationID] = {
|
||||||
|
attempts = 0,
|
||||||
|
nextAttempt = currentTime + RETRY_CONFIG.INTERVAL,
|
||||||
|
expiry = currentTime + RETRY_CONFIG.VALIDITY_DURATION
|
||||||
|
}
|
||||||
|
else
|
||||||
|
-- 延长该分代的过期时间
|
||||||
|
entry.generations[generationID].expiry = currentTime + RETRY_CONFIG.VALIDITY_DURATION
|
||||||
|
end
|
||||||
|
|
||||||
|
-- print(string.format("Added generation %d for %s", generationID, mag.Name))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not handInv then return end
|
||||||
|
|
||||||
|
local handInvSlots = handInv.slots
|
||||||
|
|
||||||
|
local function getPlayerInvItemsWithoutHand()
|
||||||
|
local playerInvItems = character.Inventory.AllItemsMod
|
||||||
|
-- 去除双手持有的物品,避免在双持情况下互相抢弹药
|
||||||
|
for i = #playerInvItems, 1, -1 do
|
||||||
|
local item = playerInvItems[i]
|
||||||
|
if (hand and item.ID == hand.ID) or (anotherhand and item.ID == anotherhand.ID) then
|
||||||
|
table.remove(playerInvItems, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return playerInvItems
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 内部堆叠实现(原tryStackMagzine拆分)
|
||||||
|
local function tryStackMagazineInternal(mag)
|
||||||
|
if not mag or mag.ConditionPercentage > 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 原有堆叠逻辑
|
||||||
|
local function tryStackInInventory(inventory, Mag)
|
||||||
|
local identifier = Mag.Prefab.Identifier
|
||||||
|
for i, slot in ipairs(inventory.slots) do
|
||||||
|
for _, item in ipairs(slot.items) do
|
||||||
|
if item.HasTag("weapon") then goto continue end
|
||||||
|
if item.Prefab.Identifier.Equals(identifier) and item.ConditionPercentage == 0 and item.ID ~= Mag.ID then -- 只有空弹匣可堆叠
|
||||||
|
if inventory.CanBePutInSlot(Mag, i-1) then
|
||||||
|
inventory.TryPutItem(Mag, i-1, false, true, nil)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 尝试玩家库存
|
||||||
|
if tryStackInInventory(character.Inventory, mag) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 尝试子容器
|
||||||
|
for item in getPlayerInvItemsWithoutHand() do
|
||||||
|
if item.OwnInventory and tryStackInInventory(item.OwnInventory, mag) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 外部入口函数(替换原tryStackMagzine)
|
||||||
|
local function tryStackMagzine(mag)
|
||||||
|
if not mag then return false end
|
||||||
|
|
||||||
|
-- 立即尝试
|
||||||
|
local success = tryStackMagazineInternal(mag)
|
||||||
|
|
||||||
|
-- 失败时加入队列
|
||||||
|
if not success then
|
||||||
|
-- 防止重复添加
|
||||||
|
addToRetryQueue(mag)
|
||||||
|
end
|
||||||
|
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
-- -- 堆叠弹匣
|
||||||
|
-- local function tryStackMagzine(Mag)
|
||||||
|
-- if Mag == nil or Mag.ConditionPercentage ~= 0 then return false end
|
||||||
|
-- local function tryStackInInventory(inventory, Mag)
|
||||||
|
-- local identifier = Mag.Prefab.Identifier
|
||||||
|
-- for i, slot in ipairs(inventory.slots) do
|
||||||
|
-- for _, item in ipairs(slot.items) do
|
||||||
|
-- if item.HasTag("weapon") then goto continue end
|
||||||
|
-- if item.Prefab.Identifier.Equals(identifier) and item.ConditionPercentage == 0 and item.ID ~= Mag.ID then -- 只有空弹匣可堆叠
|
||||||
|
-- if inventory.TryPutItem(Mag, i-1, false, true, nil) then
|
||||||
|
-- return true
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- ::continue::
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- return false
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- -- 尝试将弹匣堆叠到玩家物品栏1-10
|
||||||
|
-- if tryStackInInventory(character.Inventory, Mag) then
|
||||||
|
-- return true
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- -- 尝试将弹匣堆叠到玩家背包、衣服等子物品栏
|
||||||
|
-- for item in getPlayerInvItemsWithoutHand() do
|
||||||
|
-- if item.OwnInventory then
|
||||||
|
-- if tryStackInInventory(item.OwnInventory, Mag) then
|
||||||
|
-- return true
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- return false
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- 卸载弹匣
|
||||||
|
local function unloadMag(index)
|
||||||
|
local unloadedMag = handInvSlots[index].items[1]
|
||||||
|
|
||||||
|
-- 尝试堆叠弹匣
|
||||||
|
if tryStackMagzine(unloadedMag) then return true end
|
||||||
|
|
||||||
|
local slots = character.Inventory.slots
|
||||||
|
-- 如果都失败了,优先尝试将弹匣放入玩家背包、衣服子物品栏
|
||||||
|
for i = #slots, 1, -1 do
|
||||||
|
if i == 4 or i == 5 or i == 8 then
|
||||||
|
if character.Inventory.TryPutItem(unloadedMag, i-1, false, true, nil) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 然后尝试将弹匣放入玩家物品栏1-10
|
||||||
|
for i = #slots, 1, -1 do
|
||||||
|
if i <= 8 or i == 19 then goto continue end
|
||||||
|
if character.Inventory.CanBePutInSlot(unloadedMag, i-1) then
|
||||||
|
character.Inventory.TryPutItem(unloadedMag, i-1, false, false, nil)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 保底情况,将弹匣丢到地面,暂时视为false,目前bool未使用
|
||||||
|
unloadedMag.Drop(character, true, true)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 根据 index 构建一个含有所有可用的弹药/弹匣的 table,参数 num 是要寻找的数量
|
||||||
|
local function findAvailableItemInPlayerInv(index, num)
|
||||||
|
local itemTable = {}
|
||||||
|
|
||||||
|
for item in getPlayerInvItemsWithoutHand() do
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
-- 忽略掉所有带武器标签的物品,避免从其他武器中抢弹药
|
||||||
|
if item.HasTag("weapon") then goto continue end
|
||||||
|
if handInv.CanBePutInSlot(item, index) and item.ConditionPercentage > 0 then
|
||||||
|
if itemTable[item.Prefab.Identifier.value] == nil then itemTable[item.Prefab.Identifier.value] = {} end
|
||||||
|
|
||||||
|
table.insert(itemTable[item.Prefab.Identifier.value], item)
|
||||||
|
count = count + 1
|
||||||
|
if count >= num then break end
|
||||||
|
end
|
||||||
|
if item.OwnInventory then
|
||||||
|
for item2 in item.OwnInventory.AllItemsMod do
|
||||||
|
if handInv.CanBePutInSlot(item2, index) and item2.ConditionPercentage > 0 then
|
||||||
|
if itemTable[item2.Prefab.Identifier.value] == nil then itemTable[item2.Prefab.Identifier.value] = {} end
|
||||||
|
|
||||||
|
table.insert(itemTable[item2.Prefab.Identifier.value], item2)
|
||||||
|
count = count + 1
|
||||||
|
if count >= num then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|
||||||
|
local maxLength = 0
|
||||||
|
local maxElement = {}
|
||||||
|
for identifier, items in pairs(itemTable) do
|
||||||
|
if #items > maxLength then
|
||||||
|
maxLength = #items
|
||||||
|
maxElement = itemTable[identifier]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return maxElement
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 根据 index 寻找可用的弹匣,但不要装入unloadedMag
|
||||||
|
local function findAvailableMagInPlayerInv(index, unloadedMag)
|
||||||
|
for item in getPlayerInvItemsWithoutHand() do
|
||||||
|
-- 忽略掉所有带武器标签的物品,避免从其他武器中抢弹药
|
||||||
|
if item.HasTag("weapon") then goto continue end
|
||||||
|
if item and item.ID ~= unloadedMag.ID and handInv.CanBePutInSlot(item, index) and item.ConditionPercentage > 0 then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
if item.OwnInventory then
|
||||||
|
for item2 in item.OwnInventory.AllItemsMod do
|
||||||
|
if item2 and item2.ID ~= unloadedMag.ID and handInv.CanBePutInSlot(item2, index) and item2.ConditionPercentage > 0 then
|
||||||
|
return item2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 根据 identifier 构建一个含有所有可用的弹药/弹匣的 table,参数 num 是要寻找的数量
|
||||||
|
local function findAvailableItemWithIdentifier(identifier, num)
|
||||||
|
local findTable = {}
|
||||||
|
local count = 0
|
||||||
|
for item in getPlayerInvItemsWithoutHand() do
|
||||||
|
-- 忽略掉所有带武器标签的物品,避免从其他武器中抢弹药
|
||||||
|
if item.HasTag("weapon") then goto continue end
|
||||||
|
|
||||||
|
if item.Prefab.Identifier.Equals(identifier) then
|
||||||
|
table.insert(findTable, item)
|
||||||
|
count = count + 1
|
||||||
|
if count >= num then
|
||||||
|
return findTable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if item.OwnInventory then
|
||||||
|
for item2 in item.OwnInventory.AllItemsMod do
|
||||||
|
if item2.Prefab.Identifier.Equals(identifier) then
|
||||||
|
table.insert(findTable, item2)
|
||||||
|
count = count + 1
|
||||||
|
if count >= num then
|
||||||
|
return findTable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
return findTable
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 根据 identifier 返回一个可用于堆叠已有弹匣的物品
|
||||||
|
local function findAvailableForStackingInPlayerInv(identifier)
|
||||||
|
local itemList = {}
|
||||||
|
for item in getPlayerInvItemsWithoutHand() do
|
||||||
|
if item.HasTag("weapon") then goto continue end
|
||||||
|
if item.Prefab.Identifier.Equals(identifier) and item.ConditionPercentage > 0 then
|
||||||
|
table.insert(itemList, item)
|
||||||
|
end
|
||||||
|
if item.OwnInventory then
|
||||||
|
for item2 in item.OwnInventory.AllItemsMod do
|
||||||
|
if item2.Prefab.Identifier.Equals(identifier) and item2.ConditionPercentage > 0 then
|
||||||
|
table.insert(itemList, item2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
-- 对 itemList 依照 ConditionPercentage 进行升序排序
|
||||||
|
table.sort(itemList, function(a, b) return a.ConditionPercentage < b.ConditionPercentage end)
|
||||||
|
|
||||||
|
return itemList
|
||||||
|
end
|
||||||
|
|
||||||
|
local function putItem(item, index, isForStacking, isForSplitting)
|
||||||
|
if item == nil or item.ConditionPercentage == 0 or item == hand or item == anotherhand then return end
|
||||||
|
if not handInv.TryPutItem(item, index, isForStacking, isForSplitting, character, true, true)
|
||||||
|
then return false end -- 如果上弹失败,则返回false
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 对枪械中每个 SlotRestriction 进行处理
|
||||||
|
local itemContainer = handInv.Container
|
||||||
|
local i = math.max(itemContainer.ContainedStateIndicatorSlot + 1, 1) -- 准确定位弹匣的slot
|
||||||
|
local handInvSlotRestriction = itemContainer.slotRestrictions[i-1]
|
||||||
|
-- 空物品情况
|
||||||
|
if #handInvSlots[i].items == 0 then
|
||||||
|
for _, item in ipairs(findAvailableItemInPlayerInv(i - 1, isSlotFull(handInvSlotRestriction, handInvSlots[i]))) do
|
||||||
|
putItem(item, i - 1, false, false)
|
||||||
|
end
|
||||||
|
-- 已有可堆叠弹药的情况
|
||||||
|
elseif #handInvSlots[i].items > 0 and isSlotFull(handInvSlotRestriction, handInvSlots[i]) > 0 then
|
||||||
|
for _, item in ipairs(findAvailableItemWithIdentifier(handInvSlots[i].items[1].Prefab.Identifier, isSlotFull(handInvSlotRestriction, handInvSlots[i]))) do
|
||||||
|
putItem(item, i - 1, false, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- 已有弹匣的情况
|
||||||
|
if isSlotFull(handInvSlotRestriction, handInvSlots[i]) == 0 and #handInvSlots[i].items == 1 and handInvSlots[i].items[1].ConditionPercentage ~= 100 then
|
||||||
|
local itemlist = findAvailableForStackingInPlayerInv(handInvSlots[i].items[1].Prefab.Identifier)
|
||||||
|
local item = itemlist[1]
|
||||||
|
if (#itemlist == 1 and handInvSlots[i].items[1].ConditionPercentage == 0) or (item and item.ConditionPercentage ~=100 and handInvSlots[i].items[1].ConditionPercentage == 0) then --特殊情况,只剩一个弹匣下处理堆叠问题
|
||||||
|
unloadMag(i)
|
||||||
|
putItem(item, i - 1, true, true)
|
||||||
|
end
|
||||||
|
if not putItem(item, i - 1, true, true) then -- 如果上弹失败,卸载弹匣
|
||||||
|
local unloadedMag = handInvSlots[i].items[1]
|
||||||
|
unloadMag(i)
|
||||||
|
-- 如果此时双手武器未装备,重新装备武器
|
||||||
|
local currentHand = character.Inventory.GetItemInLimbSlot(handIEnumerable[1])
|
||||||
|
local currentAnotherHand = character.Inventory.GetItemInLimbSlot(anotherhandIEnumerable[1])
|
||||||
|
if (currentHand == hand and currentAnotherHand == anotherhand) ~= true then
|
||||||
|
if hand and anotherhand and hand.ID == anotherhand.ID then -- 如果为双手武器
|
||||||
|
for _, handSlotType in ipairs { InvSlotType.LeftHand, InvSlotType.RightHand } do
|
||||||
|
local handSlotIndex = character.Inventory.FindLimbSlot(handSlotType)
|
||||||
|
if handSlotIndex >= 0 then
|
||||||
|
character.Inventory.TryPutItem(hand, handSlotIndex, true, false, character, true, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else -- 如果为单手武器或者双持武器
|
||||||
|
character.Inventory.TryPutItem(hand, character, handIEnumerable, true, true)
|
||||||
|
character.Inventory.TryPutItem(anotherhand, character, anotherhandIEnumerable, true, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local findMag = findAvailableMagInPlayerInv(i - 1, unloadedMag)
|
||||||
|
if #itemlist == 0 and unloadedMag.ConditionPercentage > 0 and findMag == nil then
|
||||||
|
putItem(unloadedMag, i - 1, false, false)
|
||||||
|
else
|
||||||
|
putItem(findMag, i - 1, false, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tryStackMagzine(item) -- 尝试堆叠空弹匣
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 注册每帧检查,在多人游戏对tryStackMagazine进行重试
|
||||||
|
Hook.Add("think", "magazineRetrySystem", function()
|
||||||
|
if not retryQueue then return end
|
||||||
|
local currentTime = Timer.GetTime()
|
||||||
|
|
||||||
|
-- 遍历所有条目
|
||||||
|
for magID, entry in pairs(retryQueue) do
|
||||||
|
local mag = entry.mag
|
||||||
|
local hasValidGenerations = false
|
||||||
|
|
||||||
|
-- 实体有效性检查
|
||||||
|
if not mag or mag.ID ~= magID then
|
||||||
|
retryQueue[magID] = nil
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
|
||||||
|
for genID, genRecord in pairs(entry.generations) do
|
||||||
|
-- 清理过期分代
|
||||||
|
if currentTime > genRecord.expiry then
|
||||||
|
entry.generations[genID] = nil
|
||||||
|
-- print("Generation expired:", genID)
|
||||||
|
goto next_generation
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 执行重试条件检查
|
||||||
|
if currentTime >= genRecord.nextAttempt then
|
||||||
|
-- 执行重试
|
||||||
|
local success = tryStackMagazineInternal(mag)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
-- 成功时清除全部分代
|
||||||
|
retryQueue[magID] = nil
|
||||||
|
-- print("Success via generation:", genID)
|
||||||
|
goto continue
|
||||||
|
else
|
||||||
|
-- 更新重试状态
|
||||||
|
genRecord.attempts = genRecord.attempts + 1
|
||||||
|
genRecord.nextAttempt = currentTime + RETRY_CONFIG.INTERVAL
|
||||||
|
|
||||||
|
-- 超过最大尝试次数
|
||||||
|
if genRecord.attempts >= RETRY_CONFIG.MAX_ATTEMPTS then
|
||||||
|
entry.generations[genID] = nil
|
||||||
|
-- print("Max attempts for generation:", genID)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
hasValidGenerations = true
|
||||||
|
::next_generation::
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 清理空条目
|
||||||
|
if not hasValidGenerations then
|
||||||
|
retryQueue[magID] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable)
|
||||||
|
if retryQueue == nil then Hook.Remove("think", "magazineRetrySystem") end
|
||||||
|
if not PlayerInput.KeyHit(Keys.R) then return end
|
||||||
|
local Character = instance
|
||||||
|
if not Character then return end
|
||||||
|
|
||||||
|
local rightHand = Character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand)
|
||||||
|
local leftHand = Character.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand)
|
||||||
|
local rightHandIEnumerable = {InvSlotType.RightHand}
|
||||||
|
local leftHandIEnumerable = {InvSlotType.LeftHand}
|
||||||
|
|
||||||
|
if not rightHand and not leftHand then return end
|
||||||
|
|
||||||
|
if rightHand and rightHand.HasTag("weapon") then
|
||||||
|
tryPutItemsInInventory(Character, rightHand, leftHand, rightHand.OwnInventory, rightHandIEnumerable, leftHandIEnumerable)
|
||||||
|
end
|
||||||
|
|
||||||
|
if leftHand and not leftHand.Equals(rightHand) and leftHand.HasTag("weapon") then
|
||||||
|
tryPutItemsInInventory(Character, leftHand, rightHand, leftHand.OwnInventory, leftHandIEnumerable, rightHandIEnumerable)
|
||||||
|
end
|
||||||
|
end, Hook.HookMethodType.After)
|
||||||
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3413495302/PressRtoReload.png
(Stored with Git LFS)
Normal file
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3413495302/PressRtoReload.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"Lua.diagnostics.libraryFiles": "Enable",
|
||||||
|
"Lua.workspace.library": [
|
||||||
|
"D:/Projects/Barotrauma/types/client",
|
||||||
|
"D:/Projects/Barotrauma/types/shared",
|
||||||
|
],
|
||||||
|
|
||||||
|
"Lua.diagnostics.disable": [
|
||||||
|
"param-type-mismatch",
|
||||||
|
"return-type-mismatch",
|
||||||
|
"undefined-field",
|
||||||
|
"need-check-nil",
|
||||||
|
"assign-type-mismatch",
|
||||||
|
"redundant-return-value",
|
||||||
|
"missing-parameter",
|
||||||
|
"undefined-global",
|
||||||
|
"missing-return-value",
|
||||||
|
"undefined-doc-name",
|
||||||
|
"missing-return",
|
||||||
|
"cast-local-type",
|
||||||
|
"deprecated",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"launch": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "MoonSharp Attach",
|
||||||
|
"type": "moonsharp-debug",
|
||||||
|
"request": "attach",
|
||||||
|
"debugServer" : 41912
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="Press R to Reload" steamworkshopid="3413495302" corepackage="false" modversion="1.0.18" gameversion="1.7.7.0" installtime="1742817668" expectedhash="BB2C3780711FCEBBF4A73BF3AC961211" />
|
||||||
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3438745304/Assets/CUI.png
(Stored with Git LFS)
Normal file
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3438745304/Assets/CUI.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<DefaultStyles>
|
||||||
|
<CUIComponent>
|
||||||
|
<BackgroundColor>CUIPalette.Component.Background</BackgroundColor>
|
||||||
|
<Border>CUIPalette.Component.Border</Border>
|
||||||
|
<ResizeHandleColor>CUIPalette.Handle.Background</ResizeHandleColor>
|
||||||
|
<ResizeHandleGrabbedColor>CUIPalette.Handle.Grabbed</ResizeHandleGrabbedColor>
|
||||||
|
</CUIComponent>
|
||||||
|
<CUIFrame>
|
||||||
|
<BackgroundColor>CUIPalette.Frame.Background</BackgroundColor>
|
||||||
|
<OutlineColor>CUIPalette.Frame.Border</OutlineColor>
|
||||||
|
</CUIFrame>
|
||||||
|
<CUITextBlock>
|
||||||
|
<TextColor>CUIPalette.Component.Text</TextColor>
|
||||||
|
<Border>Transparent</Border>
|
||||||
|
<BackgroundColor>Transparent</BackgroundColor>
|
||||||
|
<Padding>[4,0]</Padding>
|
||||||
|
</CUITextBlock>
|
||||||
|
<CUITextInput>
|
||||||
|
<TextColor>CUIPalette.Input.Text</TextColor>
|
||||||
|
<Border>CUIPalette.Input.Border</Border>
|
||||||
|
<BackgroundColor>CUIPalette.Input.Background</BackgroundColor>
|
||||||
|
</CUITextInput>
|
||||||
|
<CUIButton>
|
||||||
|
<MasterColorOpaque>CUIPalette.Button.Background</MasterColorOpaque>
|
||||||
|
<Border>CUIPalette.Button.Border</Border>
|
||||||
|
<DisabledColor>CUIPalette.Button.Disabled</DisabledColor>
|
||||||
|
<Padding>[4,2]</Padding>
|
||||||
|
<TextAlign>[0.5,0.5]</TextAlign>
|
||||||
|
</CUIButton>
|
||||||
|
<CUICompositeButton>
|
||||||
|
<MasterColorOpaque>CUIPalette.Button.Background</MasterColorOpaque>
|
||||||
|
<DisabledColor>CUIPalette.Button.Disabled</DisabledColor>
|
||||||
|
</CUICompositeButton>
|
||||||
|
<CUIToggleButton>
|
||||||
|
<MasterColorOpaque>CUIPalette.Button.Background</MasterColorOpaque>
|
||||||
|
<Border>CUIPalette.Button.Border</Border>
|
||||||
|
<DisabledColor>CUIPalette.Button.Background</DisabledColor>
|
||||||
|
</CUIToggleButton>
|
||||||
|
<CUICloseButton>
|
||||||
|
<MasterColorOpaque>CUIPalette.CloseButton.Background</MasterColorOpaque>
|
||||||
|
<Border>Transparent</Border>
|
||||||
|
</CUICloseButton>
|
||||||
|
<DDOption>
|
||||||
|
<InactiveColor>Transparent</InactiveColor>
|
||||||
|
<Border>Transparent</Border>
|
||||||
|
<MouseOverColor>CUIPalette.DDOption.Hover</MouseOverColor>
|
||||||
|
<TextColor>CUIPalette.DDOption.Text</TextColor>
|
||||||
|
<TextAlign>[0,0]</TextAlign>
|
||||||
|
<Padding>[4,0]</Padding>
|
||||||
|
</DDOption>
|
||||||
|
<CUISlider>
|
||||||
|
<BackgroundColor>Transparent</BackgroundColor>
|
||||||
|
<Border>Transparent</Border>
|
||||||
|
</CUISlider>
|
||||||
|
<CUITickBox>
|
||||||
|
<BackgroundColor>CUIPalette.Main.Text</BackgroundColor>
|
||||||
|
</CUITickBox>
|
||||||
|
<CUICanvas>
|
||||||
|
<BackgroundColor>White</BackgroundColor>
|
||||||
|
<Border>Black</Border>
|
||||||
|
</CUICanvas>
|
||||||
|
<CUIHorizontalList>
|
||||||
|
<BackgroundColor>Transparent</BackgroundColor>
|
||||||
|
</CUIHorizontalList>
|
||||||
|
<CUIVerticalList>
|
||||||
|
<BackgroundColor>Transparent</BackgroundColor>
|
||||||
|
</CUIVerticalList>
|
||||||
|
<CUIPages>
|
||||||
|
<BackgroundColor>Transparent</BackgroundColor>
|
||||||
|
<Border>Transparent</Border>
|
||||||
|
</CUIPages>
|
||||||
|
</DefaultStyles>
|
||||||
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3438745304/Assets/Interaction icons sharp.png
(Stored with Git LFS)
Normal file
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3438745304/Assets/Interaction icons sharp.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3438745304/Assets/Interaction icons.png
(Stored with Git LFS)
Normal file
BIN
Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/3438745304/Assets/Interaction icons.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<CUIFrame Absolute="[0,0,180,500]" Anchor="[0.5,0.5]">
|
||||||
|
<CUIVerticalList AKA="list" Relative="[0,0,1,1]">
|
||||||
|
<CUIHorizontalList AKA="caption" BackgroundColor="127,0,0,255" Border="127,0,0,255" Direction="Reverse" FitContent="[False,True]" Style="{ BackgroundColor : CUIPalette.Frame.Border, Border : CUIPalette.Frame.Border, TextColor : CUIPalette.Frame.Text }">
|
||||||
|
<CUICloseButton AKA="close" />
|
||||||
|
<CUITextBlock AKA="text" BackgroundColor="127,0,0,255" Border="127,0,0,255" FillEmptySpace="[True,False]" Text="caption" TextAlign="[0,0.5]" TextColor="255,229,229,255" />
|
||||||
|
</CUIHorizontalList>
|
||||||
|
<CUIHorizontalList AKA="header" BackgroundColor="76,0,0,255" Border="102,0,0,255" Direction="Reverse" FitContent="[False,True]" Style="{ BackgroundColor : CUIPalette.Header.Background, Border : CUIPalette.Header.Border, TextColor : CUIPalette.Header.Text }">
|
||||||
|
<CUITextBlock AKA="text" BackgroundColor="76,0,0,255" Border="102,0,0,255" FillEmptySpace="[True,False]" Style="{ BackgroundColor : CUIPalette.Header.Background, Border : CUIPalette.Header.Border, TextColor : CUIPalette.Header.Text }" Text="Header" TextAlign="[0,0.5]" TextColor="255,229,229,255" />
|
||||||
|
</CUIHorizontalList>
|
||||||
|
<CUIHorizontalList AKA="nav" BackgroundColor="51,0,0,255" Border="76,0,0,255" Direction="Reverse" FitContent="[False,True]" Style="{ BackgroundColor : CUIPalette.Nav.Background, Border : CUIPalette.Nav.Border, TextColor : CUIPalette.Nav.Text }">
|
||||||
|
<CUITextBlock AKA="text" BackgroundColor="51,0,0,255" Border="76,0,0,255" FillEmptySpace="[True,False]" Style="{ BackgroundColor : CUIPalette.Nav.Background, Border : CUIPalette.Nav.Border, TextColor : CUIPalette.Nav.Text }" Text="Nav" TextAlign="[0,0.5]" />
|
||||||
|
</CUIHorizontalList>
|
||||||
|
<CUIVerticalList AKA="main" BackgroundColor="25,0,0,255" Border="51,0,0,255" FillEmptySpace="[False,True]" Style="{ BackgroundColor : CUIPalette.Main.Background, Border : CUIPalette.Main.Border, TextColor : CUIPalette.Main.Text }">
|
||||||
|
<CUITextBlock AKA="text" BackgroundColor="25,0,0,255" Border="51,0,0,255" Style="{ BackgroundColor : CUIPalette.Main.Background, Border : CUIPalette.Main.Border, TextColor : CUIPalette.Main.Text }" Text="Main" TextAlign="[0,0.5]" />
|
||||||
|
<CUIButton Text="button" />
|
||||||
|
<CUIToggleButton Text="button" />
|
||||||
|
<CUITextInput Absolute="[,,,22]" />
|
||||||
|
<CUIDropDown Options="[123,321,weqwerqwe]" Selected="123" />
|
||||||
|
</CUIVerticalList>
|
||||||
|
<CUIComponent AKA="filler" Absolute="[,,,30]" />
|
||||||
|
</CUIVerticalList>
|
||||||
|
</CUIFrame>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PaletteSet Name="Blue">
|
||||||
|
<Palette Name="blue1" BaseColor="0,0,255,255">
|
||||||
|
<Frame Background="0,0,0,200" Border="0,0,127,227" Text="229,229,255,255" />
|
||||||
|
<Header Background="0,0,76,216" Border="0,0,102,222" Text="229,229,255,255" />
|
||||||
|
<Nav Background="0,0,51,211" Border="0,0,76,216" Text="204,204,255,255" />
|
||||||
|
<Main Background="0,0,25,205" Border="0,0,51,211" Text="204,204,255,255" />
|
||||||
|
<Component Background="0,0,0,0" Border="0,0,0,0" Text="204,204,255,255" />
|
||||||
|
<Button Background="0,0,255,255" Border="0,0,127,227" Disabled="12,12,63,255" />
|
||||||
|
<CloseButton Background="51,51,255,255" />
|
||||||
|
<DDOption Background="0,0,76,216" Border="0,0,51,211" Hover="0,0,127,227" Text="204,204,255,255" />
|
||||||
|
<Handle Background="51,51,152,232" Grabbed="51,51,255,255" />
|
||||||
|
<Slider>178,178,255,255</Slider>
|
||||||
|
<Input Background="0,0,51,211" Border="0,0,76,216" Text="204,204,255,255" Focused="0,0,255,255" Invalid="255,0,0,255" Valid="0,255,0,255" Selection="178,178,255,127" Caret="178,178,255,127" />
|
||||||
|
</Palette>
|
||||||
|
<Palette Name="blue2" BaseColor="64,0,255,255">
|
||||||
|
<Frame Background="0,0,0,200" Border="32,0,127,227" Text="235,229,255,255" />
|
||||||
|
<Header Background="19,0,76,216" Border="25,0,102,222" Text="235,229,255,255" />
|
||||||
|
<Nav Background="12,0,51,211" Border="19,0,76,216" Text="216,204,255,255" />
|
||||||
|
<Main Background="6,0,25,205" Border="12,0,51,211" Text="216,204,255,255" />
|
||||||
|
<Component Background="0,0,0,0" Border="0,0,0,0" Text="216,204,255,255" />
|
||||||
|
<Button Background="64,0,255,255" Border="32,0,127,227" Disabled="25,12,63,255" />
|
||||||
|
<CloseButton Background="102,51,255,255" />
|
||||||
|
<DDOption Background="19,0,76,216" Border="12,0,51,211" Hover="32,0,127,227" Text="216,204,255,255" />
|
||||||
|
<Handle Background="76,51,152,232" Grabbed="102,51,255,255" />
|
||||||
|
<Slider>197,178,255,255</Slider>
|
||||||
|
<Input Background="12,0,51,211" Border="19,0,76,216" Text="216,204,255,255" Focused="64,0,255,255" Invalid="255,0,0,255" Valid="0,255,0,255" Selection="197,178,255,127" Caret="197,178,255,127" />
|
||||||
|
</Palette>
|
||||||
|
<Palette Name="blue3" BaseColor="0,128,255,255">
|
||||||
|
<Frame Background="0,0,0,200" Border="0,64,127,227" Text="229,242,255,255" />
|
||||||
|
<Header Background="0,38,76,216" Border="0,51,102,222" Text="229,242,255,255" />
|
||||||
|
<Nav Background="0,25,51,211" Border="0,38,76,216" Text="204,229,255,255" />
|
||||||
|
<Main Background="0,12,25,205" Border="0,25,51,211" Text="204,229,255,255" />
|
||||||
|
<Component Background="0,0,0,0" Border="0,0,0,0" Text="204,229,255,255" />
|
||||||
|
<Button Background="0,128,255,255" Border="0,64,127,227" Disabled="12,38,63,255" />
|
||||||
|
<CloseButton Background="51,153,255,255" />
|
||||||
|
<DDOption Background="0,38,76,216" Border="0,25,51,211" Hover="0,64,127,227" Text="204,229,255,255" />
|
||||||
|
<Handle Background="51,102,152,232" Grabbed="51,153,255,255" />
|
||||||
|
<Slider>178,216,255,255</Slider>
|
||||||
|
<Input Background="0,25,51,211" Border="0,38,76,216" Text="204,229,255,255" Focused="0,128,255,255" Invalid="255,0,0,255" Valid="0,255,0,255" Selection="178,216,255,127" Caret="178,216,255,127" />
|
||||||
|
</Palette>
|
||||||
|
<Palette Name="blue4" BaseColor="128,0,255,255">
|
||||||
|
<Frame Background="0,0,0,200" Border="64,0,127,227" Text="242,229,255,255" />
|
||||||
|
<Header Background="38,0,76,216" Border="51,0,102,222" Text="242,229,255,255" />
|
||||||
|
<Nav Background="25,0,51,211" Border="38,0,76,216" Text="229,204,255,255" />
|
||||||
|
<Main Background="12,0,25,205" Border="25,0,51,211" Text="229,204,255,255" />
|
||||||
|
<Component Background="0,0,0,0" Border="0,0,0,0" Text="229,204,255,255" />
|
||||||
|
<Button Background="128,0,255,255" Border="64,0,127,227" Disabled="38,12,63,255" />
|
||||||
|
<CloseButton Background="153,51,255,255" />
|
||||||
|
<DDOption Background="38,0,76,216" Border="25,0,51,211" Hover="64,0,127,227" Text="229,204,255,255" />
|
||||||
|
<Handle Background="102,51,152,232" Grabbed="153,51,255,255" />
|
||||||
|
<Slider>216,178,255,255</Slider>
|
||||||
|
<Input Background="25,0,51,211" Border="38,0,76,216" Text="229,204,255,255" Focused="128,0,255,255" Invalid="255,0,0,255" Valid="0,255,0,255" Selection="216,178,255,127" Caret="216,178,255,127" />
|
||||||
|
</Palette>
|
||||||
|
</PaletteSet>
|
||||||
@@ -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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user