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