Compare commits

...

10 Commits

Author SHA1 Message Date
424bed32d1 Rename to cykaquick 2025-03-31 13:14:09 +02:00
2d9f3cabe4 Add readme, such as it is 2025-03-31 13:13:29 +02:00
11be034c8f Add the network param to all tryputitem 2025-03-31 12:49:29 +02:00
4dc465aa46 Fix rubberbanding on ladders 2025-03-31 12:26:47 +02:00
d7ceb9a4d6 Fix rubberbanding and refactor a little 2025-03-31 12:09:21 +02:00
79bfa72afd Fix access range 2025-03-31 12:02:54 +02:00
f21d288525 Add quick access
Now we're telepathic
2025-03-31 11:57:43 +02:00
b7d4531ec8 Add quick interactions locally 2025-03-31 04:02:39 +02:00
5bb7a207f2 Minor changes 2025-03-31 03:59:15 +02:00
1b713a858c First stack submarine items then player items 2025-03-31 03:52:02 +02:00
155 changed files with 15719 additions and 58 deletions

View File

@@ -22,10 +22,13 @@ MyModGlobal = {
LOOT = Keys.L,
SONAR = Keys.X,
AOEPICKUP = Keys.Y,
QICK_FABRICATOR = Keys.K,
QICK_DECONSTRUCTOR = Keys.J,
QICK_MEDICAL_FABRICATOR = Keys.M,
NESTED_CONTAINERS = true,
DEBUG_MODE = true,
},
MOD_NAME = "Quick Stack To Containers",
MOD_NAME = "Cyka Quick",
MOD_VERSION = "1.1.0",
BAG_SLOT = 8,
}
@@ -63,13 +66,16 @@ local quickbuy = require("Cyka.quickbuy")
local hotkeyrepair = require("Cyka.hotkeyrepair")
local cursormacroer = require("Cyka.cursormacroer")
local quickunload = require("Cyka.quickunload")
local quickreload= require("Cyka.quickreload")
local quickreload = require("Cyka.quickreload")
local quickloot = require("Cyka.quickloot")
local sonarpinger = require("Cyka.sonarpinger")
local aoepickup = require("Cyka.aoepickup")
local quickaccess = require("Cyka.quickaccess")
require("Cyka.xpticker")
require("Cyka.zoom")
-- TODO: Keybind fabricator / medical fabricator / deconstructor on the sub
print(MyModGlobal.MOD_NAME .. " v" .. MyModGlobal.MOD_VERSION .. " loaded!")
-- Register necessary types and make fields accessible
@@ -158,3 +164,18 @@ Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptab
if not PlayerInput.KeyHit(MyModGlobal.CONFIG.AOEPICKUP) then return end
aoepickup.tryAoePickup()
end, Hook.HookMethodType.After)
Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable)
if not PlayerInput.KeyHit(MyModGlobal.CONFIG.QICK_FABRICATOR) then return end
quickaccess.tryAccessFabricator(PlayerInput.IsShiftDown())
end, Hook.HookMethodType.After)
Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable)
if not PlayerInput.KeyHit(MyModGlobal.CONFIG.QICK_DECONSTRUCTOR) then return end
quickaccess.tryAccessDeconstructor(PlayerInput.IsShiftDown())
end, Hook.HookMethodType.After)
Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable)
if not PlayerInput.KeyHit(MyModGlobal.CONFIG.QICK_MEDICAL_FABRICATOR) then return end
quickaccess.tryAccessMedicalFabricator(PlayerInput.IsShiftDown())
end, Hook.HookMethodType.After)

View File

@@ -129,7 +129,7 @@ local function tryAoePickup()
return
end
local distanceThreshold = 500
local distanceThreshold = 2000
local characterPos = character.WorldPosition
local nearbyItems = getNearbyItems(characterPos, distanceThreshold)

View File

@@ -167,7 +167,7 @@ local function tryStackFabricator(character)
local previous = nil
for _, item in ipairs(items) do
if previous ~= item.Prefab.Identifier then slot = slot + 1 end
local moved = inputInventory.TryPutItem(item, slot, false, true, nil)
local moved = inputInventory.TryPutItem(item, slot, false, true, Character.Controlled, true)
if not moved then
MyModGlobal.debugPrint(string.format("Failed to move %s", item.Prefab.Identifier.Value))
end

View File

@@ -0,0 +1,113 @@
-- luacheck: globals MyModGlobal Character Hook EnsurePatch
local utils = require "Cyka.utils"
local dump = require "Cyka.dump"
local machines = "fabricator,deconstructor,medicalfabricator"
Hook.Patch("Cyka", "Barotrauma.Character", "CanInteractWith",
{ "Barotrauma.Item", "System.Single&", "System.Boolean" },
function(_, ptable)
local item = ptable["item"]
if machines:find(tostring(item.Prefab.Identifier.Value)) then
ptable.ReturnValue = true
end
end,
Hook.HookMethodType.After
)
local cachedFabricator = nil
local cachedDeconstructor = nil
local cachedMedicalFabricator = nil
local function select(item)
MyModGlobal.debugPrint("Selecting " .. tostring(item))
item.Prefab.GrabWhenSelected = false
Character.Controlled.SelectedItem = item
end
---@param prefabIdentifier string
---@param componentName string
---@return Barotrauma.Item, Barotrauma.Items.Components
local function findByComponent(prefabIdentifier, componentName)
local items = utils.enqueueAllSubmarineItems({}, function(item)
return item.Prefab.Identifier.Value == prefabIdentifier, item.Prefab.Identifier.Value == prefabIdentifier
end)
if #items == 0 then
MyModGlobal.debugPrint("No " .. componentName .. " found")
return nil, nil
end
if #items > 1 then
MyModGlobal.debugPrint("Multiple " .. componentName .. " found, using first one")
return nil, nil
end
---@type Barotrauma.Item
local item = items[1]
---@type Barotrauma.Items.Components
local component
for itcomponent in item.Components do
if itcomponent.Name == componentName then
component = itcomponent
break
end
end
if not component then
MyModGlobal.debugPrint("No " .. componentName .. " component found")
return
end
return item, component
end
---@param force boolean
local function tryAccessFabricator(force)
local fabricator = cachedFabricator
if not fabricator or force then
fabricator = findByComponent("fabricator", "Fabricator")
if not fabricator then
MyModGlobal.debugPrint("No fabricator found")
return
end
cachedFabricator = fabricator
end
MyModGlobal.debugPrint("Selecting fabricator")
select(fabricator)
end
---@param force boolean
local function tryAccessDeconstructor(force)
local deconstructor = cachedDeconstructor
if not deconstructor or force then
deconstructor = findByComponent("deconstructor", "Deconstructor")
if not deconstructor then
MyModGlobal.debugPrint("No deconstructor found")
return
end
cachedDeconstructor = deconstructor
end
MyModGlobal.debugPrint("Selecting deconstructor")
select(deconstructor)
end
---@param force boolean
local function tryAccessMedicalFabricator(force)
local medicalFabricator = cachedMedicalFabricator
if not medicalFabricator or force then
medicalFabricator = findByComponent("medicalfabricator", "MedicalFabricator")
if not medicalFabricator then
MyModGlobal.debugPrint("No medical fabricator found")
return
end
cachedMedicalFabricator = medicalFabricator
end
MyModGlobal.debugPrint("Selecting medical fabricator")
select(medicalFabricator)
end
return {
tryAccessFabricator = tryAccessFabricator,
tryAccessDeconstructor = tryAccessDeconstructor,
tryAccessMedicalFabricator = tryAccessMedicalFabricator,
}

View File

@@ -162,7 +162,7 @@ local function tryReloadSlot(slot, preferMinCondition)
break
end
local moved = inventorySlot.inventory.TryPutItem(ititem, inventorySlot.slotIndex, false, true, nil)
local moved = inventorySlot.inventory.TryPutItem(ititem, inventorySlot.slotIndex, false, true, Character.Controlled, true)
-- When the slot is full no more will be able to be moved
-- And tat that point we're done with that slot
if not moved then break end

View File

@@ -11,6 +11,7 @@ local LOAD_MAP = {
},
anechoicdivingsuit = { oxygentank = 1, },
handheldsonar = { batterycell = 1 },
underwaterscooter = { batterycell = 1 },
}
return LOAD_MAP

View File

@@ -122,7 +122,7 @@ local function tryMoveItem(item, itemTree, force)
-- MyModGlobal.debugPrint(string.format("Can be put in slot %d: %s", itemLocation.slotIndex, tostring(canBePut)))
if itemLocation.maxFits > 0 and canBePut then
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, false, true, nil)
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, false, true, Character.Controlled, true)
if moved then
itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex)
end
@@ -150,7 +150,7 @@ local function tryMoveItem(item, itemTree, force)
if maxFits > 0 then
-- MyModGlobal.debugPrint(string.format("Trying to move item to empty slot at index: %d", itemLocation.slotIndex))
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, true, false, nil)
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, true, false, Character.Controlled, true)
if moved then
itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex)
end
@@ -326,7 +326,7 @@ local function stackToCursor()
-- UPDATE: OK well that was a stupid idea, it returns an error for other shit as well
-- What other shit? Wish I knew
-- So we'll use HowManyCanBePut instead...
local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, nil)
local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, Character.Controlled, true)
if not moved then
MyModGlobal.debugPrint(string.format("Failed to move item %s to slot %d", ititem.Name, slot
.slotIndex - 1))
@@ -353,7 +353,7 @@ local function stackAllToCursor()
end
for _, slot in ipairs(slots) do
local item
local item, predicate
if not slot.slot.items or #slot.slot.items == 0 then
MyModGlobal.debugPrint("No items in slot")
goto continue
@@ -361,7 +361,7 @@ local function stackAllToCursor()
item = slot.slot.items[1]
MyModGlobal.debugPrint(string.format("Stacking all items to %s", item.Prefab.Identifier.Value))
utils.enqueueAllOwnedItems({}, function(ititem)
predicate = function(ititem)
if ititem.Prefab.Identifier.Value == item.Prefab.Identifier.Value then
if item == ititem then return false end
-- We are moving items in the predicate because we expect to only
@@ -372,7 +372,7 @@ local function stackAllToCursor()
-- UPDATE: OK well that was a stupid idea, it returns an error for other shit as well
-- What other shit? Wish I knew
-- So we'll use HowManyCanBePut instead...
local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, nil)
local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, Character.Controlled, true)
if not moved then
MyModGlobal.debugPrint(string.format("Failed to move item %s to slot %d", ititem.Name, slot
.slotIndex - 1))
@@ -385,7 +385,10 @@ local function stackAllToCursor()
return false, true
end
end
end)
end
utils.enqueueAllSubmarineItems({}, predicate)
utils.enqueueAllPlayerItems({}, predicate)
::continue::
end

View File

@@ -96,7 +96,7 @@ local function tryUnloadSlot(slot)
for _, nearbySlot in ipairs(nearbySlots) do
local canAccept = nearbySlot.inventory.CanBePutInSlot(iitem.Prefab, nearbySlot.slotIndex)
if canAccept then
local moved = nearbySlot.inventory.TryPutItem(iitem, nearbySlot.slotIndex, true, false, nil)
local moved = nearbySlot.inventory.TryPutItem(iitem, nearbySlot.slotIndex, true, false, Character.Controlled, true)
-- print(string.format("Moved item %s to slot %d", iitem.Name, nearbySlot.slotIndex))
if moved then break end
end

74
CykaQuick/README.md Normal file
View File

@@ -0,0 +1,74 @@
# Quick Stack To Containers
A Barotrauma mod that allows you to quickly stack items from your inventory into containers by pressing the F key.
## Features
- **Quick Stack:** Press F to quickly stack items from your inventory into containers.
- Supports stacking into multiple containers in your inventory (backpacks, pouches, toolbelts, etc.).
- Prioritizes the main backpack in the bag slot.
- Items are stacked with matching items in containers when possible.
- Automatically skips hand slots and diving gear.
- Provides feedback messages in chat.
- **Fabricator Stack:** Press V to quickly stack items from your inventory into the fabricator.
- **Quick Buy:** Press B to quickly buy items from the store.
- **Hotkey Repair:** Press R to quickly repair items.
- **Cursor Macroer (Stack to Cursor):** Hold ALT and click to stack items to the item under the cursor. Mouse4 to set target inventory.
- **Quick Unload:** Press E to quickly unload items from the cursor.
- **Quick Reload:** Press R to quickly reload items. Hold SHIFT to reload all items.
- **Quick Loot:** Press L to quickly loot items.
- **Sonar Pinger:** Press X to quickly ping the sonar.
- **AOE Pickup:** Press Y to quickly pickup items in an area.
- **Quick Access:**
- Press K to quickly access the fabricator.
- Press J to quickly access the deconstructor.
- Press M to quickly access the medical fabricator.
## Installation
1. Place the "QuickStackToBag" folder in your Barotrauma's "LocalMods" directory
2. Enable the mod in the game's mod menu
## Configuration
You can modify the following settings in the `Lua/Autorun/init.lua` file:
- `QUICKSTACK_KEYS`: The key to press for quick stacking (default: F)
- `FABRICATOR_KEY`: The key to press for quick stacking into the fabricator (default: V)
- `MAX_BUY`: The key to press for quick buying items from the store (default: B)
- `FIX`: The key to press for quick repairing items (default: R)
- `UNLOAD`: The key to press for quick unloading items from the cursor (default: E)
- `RELOAD`: The key to press for quick reloading items (default: R)
- `STACK_TO_CURSOR`: The key to press for stacking items to the cursor (default: G)
- `LOOT`: The key to press for quick looting items (default: L)
- `SONAR`: The key to press for quick pinging the sonar (default: X)
- `AOEPICKUP`: The key to press for quick picking up items in an area (default: Y)
- `QICK_FABRICATOR`: The key to press for quick accessing the fabricator (default: K)
- `QICK_DECONSTRUCTOR`: The key to press for quick accessing the deconstructor (default: J)
- `QICK_MEDICAL_FABRICATOR`: The key to press for quick accessing the medical fabricator (default: M)
- `NESTED_CONTAINERS`: Whether to check nested containers for stacking (default: true)
- `DEBUG_MODE`: Whether to enable debug mode (default: true)
- `BAG_SLOT`: The slot number for the primary bag (default: 8)
## How It Works
- **Quick Stack:** When you press F:
1. The mod identifies all containers in your inventory
2. It then checks each item in your inventory (except those in your hands or diving gear)
3. For each item, it tries to stack it into a matching stack in a container
4. If no matching stack is found, it tries to place it in any valid slot
5. Items are moved in order of container priority (main backpack first, then other containers)
- **Fabricator Stack:** When you press V, the mod attempts to stack items from your inventory (and then the entire submarine) into the connected fabricator to match the recipe.
- **Quick Buy:** When you press B, the mod attempts to buy the maximum amount of the item under the cursor from the store.
- **Hotkey Repair:** When you press R, the mod attempts to repair the item under the cursor mimicking clicking the repair button once.
- **Cursor Macroer (Stack to Cursor):** When you hold ALT and mouse over items, the mod attempts to move each item under the cursor to the target inventory.
- Press mouse4 over an inventory to set it as the target.
- **Quick Unload:** When you press E, the mod attempts to unload the item under the cursor.
- **Quick Reload:** When you press R, the mod attempts to reload the item under the cursor.
- **Quick Loot:** When you press L, the mod attempts to loot items from dead creatures from the entire map.
- **Sonar Pinger:** When you press X, the mod attempts to ping the sonar while you are the captain.
- **AOE Pickup:** When you press Y, the mod attempts to pickup items in an area.
- **Quick Access:**
- When you press K, the mod attempts to open the submarine's fabricator.
- When you press J, the mod attempts to open the submarine's deconstructor.
- When you press M, the mod attempts to open the submarine's medical fabricator.

BIN
Quick Interactions/Assets/CUI.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -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
Quick Interactions/Assets/Interaction icons sharp.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Quick Interactions/Assets/Interaction icons.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}");
}
}

View File

@@ -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();
};
}
}
}

View File

@@ -0,0 +1,301 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.IO;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using HarmonyLib;
using System.Runtime.CompilerServices;
[assembly: IgnoresAccessChecksTo("Barotrauma")]
[assembly: IgnoresAccessChecksTo("DedicatedServer")]
[assembly: IgnoresAccessChecksTo("BarotraumaCore")]
namespace QICrabUI
{
/// <summary>
/// In fact a static class managing static things
/// </summary>
public partial class CUI
{
public static Vector2 GameScreenSize => new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
public static Rectangle GameScreenRect => new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
private static string modDir;
public static string ModDir
{
get => modDir;
set
{
modDir = value;
LuaFolder = Path.Combine(value, @"Lua");
}
}
public static bool UseLua { get; set; } = true;
public static string LuaFolder { get; set; }
private static string assetsPath;
public static string AssetsPath
{
get => assetsPath;
set
{
assetsPath = value;
PalettesPath = Path.Combine(value, @"Palettes");
}
}
public static string CUITexturePath = "CUI.png";
public static string PalettesPath { get; set; }
/// <summary>
/// If set CUI will also check this folder when loading textures
/// </summary>
public static string PGNAssets
{
get => TextureManager.PGNAssets;
set => TextureManager.PGNAssets = value;
}
private static List<CUI> Instances = new List<CUI>();
/// <summary>
/// The singleton
/// </summary>
public static CUI Instance
{
get
{
if (Instances.Count == 0) return null;
return Instances.First();
}
set
{
Instances.Clear();
if (value != null) Instances.Add(value);
}
}
/// <summary>
/// Orchestrates Drawing and updates, there could be only one
/// CUI.Main is located under vanilla GUI
/// </summary>
public static CUIMainComponent Main => Instance?.main;
/// <summary>
/// Orchestrates Drawing and updates, there could be only one
/// CUI.TopMain is located above vanilla GUI
/// </summary>
public static CUIMainComponent TopMain => Instance?.topMain;
/// <summary>
/// Snapshot of mouse and keyboard state
/// </summary>
public static CUIInput Input => Instance?.input;
/// <summary>
/// Safe texture manager
/// </summary
public static CUITextureManager TextureManager => Instance?.textureManager;
/// <summary>
/// Adapter to vanilla focus system, don't use
/// </summary>
public static CUIFocusResolver FocusResolver => Instance?.focusResolver;
public static CUILuaRegistrar LuaRegistrar => Instance?.luaRegistrar;
public static CUIComponent FocusedComponent
{
get => FocusResolver.FocusedCUIComponent;
set => FocusResolver.FocusedCUIComponent = value;
}
/// <summary>
/// This affects logging
/// </summary>
public static bool Debug;
/// <summary>
/// Will break the mod if it's compiled
/// </summary>
public static bool UseCursedPatches { get; set; } = false;
/// <summary>
/// It's important to set it, if 2 CUIs try to add a hook with same id one won't be added
/// </summary>
public static string HookIdentifier
{
get => hookIdentifier;
set
{
hookIdentifier = value?.Replace(' ', '_');
}
}
private static string hookIdentifier = "";
public static string CUIHookID => $"QICrabUI.{HookIdentifier}";
public static Harmony harmony;
public static Random Random = new Random();
/// <summary>
/// Called on first Initialize
/// </summary>
public static event Action OnInit;
/// <summary>
/// Called on last Dispose
/// </summary>
public static event Action OnDispose;
public static bool Disposed { get; set; } = true;
public static event Action<TextInputEventArgs> OnWindowTextInput;
public static event Action<TextInputEventArgs> OnWindowKeyDown;
//public static event Action<TextInputEventArgs> OnWindowKeyUp;
//TODO this doesn't trigger when you press menu button, i need to go inside thet method
public static event Action OnPauseMenuToggled;
public static void InvokeOnPauseMenuToggled() => OnPauseMenuToggled?.Invoke();
public static bool InputBlockingMenuOpen
{
get
{
if (IsBlockingPredicates == null) return false;
return IsBlockingPredicates.Any(p => p());
}
}
public static List<Func<bool>> IsBlockingPredicates => Instance?.isBlockingPredicates;
private List<Func<bool>> isBlockingPredicates = new List<Func<bool>>();
/// <summary>
/// In theory multiple mods could use same CUI instance,
/// i clean it up when UserCount drops to 0
/// </summary>
public static int UserCount = 0;
/// <summary>
/// An object that contains current mouse and keyboard states
/// It scans states at the start on Main.Update
/// </summary>
private CUIInput input = new CUIInput();
private CUIMainComponent main;
private CUIMainComponent topMain;
private CUITextureManager textureManager = new CUITextureManager();
private CUIFocusResolver focusResolver = new CUIFocusResolver();
private CUILuaRegistrar luaRegistrar = new CUILuaRegistrar();
public static void ReEmitWindowTextInput(object sender, TextInputEventArgs e) => OnWindowTextInput?.Invoke(e);
public static void ReEmitWindowKeyDown(object sender, TextInputEventArgs e) => OnWindowKeyDown?.Invoke(e);
//public static void ReEmitWindowKeyUp(object sender, TextInputEventArgs e) => OnWindowKeyUp?.Invoke(e);
private void CreateMains()
{
main = new CUIMainComponent() { AKA = "Main Component" };
topMain = new CUIMainComponent() { AKA = "Top Main Component" };
}
/// <summary>
/// Should be called in IAssemblyPlugin.Initialize
/// \todo make it CUI instance member when plugin system settle
/// </summary>
public static void Initialize()
{
CUIDebug.Log($"CUI.Initialize {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
if (Instance == null)
{
Disposed = false;
Instance = new CUI();
Stopwatch sw = Stopwatch.StartNew();
if (HookIdentifier == null || HookIdentifier == "") CUI.Warning($"Warning: CUI.HookIdentifier is not set, this mod may conflict with other mods that use CUI");
InitStatic();
// this should init only static stuff that doesn't depend on instance
OnInit?.Invoke();
Instance.CreateMains();
GameMain.Instance.Window.TextInput += ReEmitWindowTextInput;
GameMain.Instance.Window.KeyDown += ReEmitWindowKeyDown;
//GameMain.Instance.Window.KeyUp += ReEmitWindowKeyUp;
CUIDebug.Log($"CUI.OnInit?.Invoke took {sw.ElapsedMilliseconds}ms");
sw.Restart();
harmony = new Harmony(CUIHookID);
PatchAll();
CUIDebug.Log($"CUI.PatchAll took {sw.ElapsedMilliseconds}ms");
AddCommands();
sw.Restart();
LuaRegistrar.Register();
CUIDebug.Log($"CUI.LuaRegistrar.Register took {sw.ElapsedMilliseconds}ms");
}
UserCount++;
CUIDebug.Log($"CUI.Initialized {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
}
public static void OnLoadCompleted()
{
//Idk doesn't work
//CUIMultiModResolver.FindOtherInputs();
}
/// <summary>
/// Should be called in IAssemblyPlugin.Dispose
/// </summary>
public static void Dispose()
{
CUIDebug.Log($"CUI.Dispose {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
UserCount--;
if (UserCount <= 0)
{
RemoveCommands();
// harmony.UnpatchAll(harmony.Id);
harmony.UnpatchAll();
TextureManager.Dispose();
CUIDebugEventComponent.CapturedIDs.Clear();
OnDispose?.Invoke();
Disposed = true;
Instance.isBlockingPredicates.Clear();
Errors.Clear();
LuaRegistrar.Deregister();
Instance = null;
UserCount = 0;
CUIDebug.Log($"CUI.Disposed {HookIdentifier} Instance:[{Instance?.GetHashCode()}] UserCount:{UserCount}", Color.Lime);
}
GameMain.Instance.Window.TextInput -= ReEmitWindowTextInput;
GameMain.Instance.Window.KeyDown -= ReEmitWindowKeyDown;
//GameMain.Instance.Window.KeyUp -= ReEmitWindowKeyUp;
}
//HACK Why it's set to run in static constructor?
// it runs perfectly fine in CUI.Initialize
//TODO component inits doesn't depend on the order
// why am i responsible for initing them here?
internal static void InitStatic()
{
CUIExtensions.InitStatic();
CUIInterpolate.InitStatic();
CUIAnimation.InitStatic();
CUIReflection.InitStatic();
CUIMultiModResolver.InitStatic();
CUIPalette.InitStatic();
CUIMap.CUIMapLink.InitStatic();
CUIMenu.InitStatic();
CUIComponent.InitStatic();
CUITypeMetaData.InitStatic();
CUIStyleLoader.InitStatic();
}
}
}

View File

@@ -0,0 +1,208 @@
## 0.2.5.1
Experimenting with the way multiple CUIs resolve conflicts
Renamed CUI.UpdateHookIdentifier => CUI.HookIdentifier
now i'm using it in harmony patches to
added warning if it's not set
fixed crash in GUI_UpdateMouseOn_Postfix
Added null checks in GUI_UpdateMouseOn_Postfix
## 0.2.5.0
Added CUI.UpdateHookIdentifier it will be set as identifier to CUI think hook, it very important to set it or hooks from different CUIs will conflict
Added CUIAnimation
Added IgnoreTransparent prop, if true mouse events will work only on not transparent sprites
Added Transparency prop, it multiplies BackgroundColor.A and is propagated to Children
Made CUISpite an object... again
Added Rotation, Origin and Offset to CUISprite
Added option to load CUISprites with base folder, which allows deserialized components to load sprites from the same folder with relative paths
Added CUIMenu, check "Custom Menus" mod, CUIRadialMenu (the ugly brother of CUIMenu)
Added more docs
## 0.2.4.0
"Fixed" cursed bug that made MainComponents become in GameMain.Update patch after multiple lobbies in compiled version
But this "fix" seems to decrease update smoothness, so i might rethink later
Set CUI.UseCursedPatches to true if you're not affraid
Added more performance measurements, shortcutted dumb class scanning in CUILuaRegistrar that happened even if you didn't use lua
Buttons now update their color only on events and not in draw cycle, added AutoUpdateColor to prevent this color change in case you want to control it manually (why?)
Added confusing event InvokeOnMouseOff which is symmetrical to InvokeOnMouseOn but happens on previous MouseOn list, and it turned out to be essential to e.g. switch color when mouse leaves a button
You can now limit resize directions with CUIComponent.ResizeDirection
Fixed forsed size not reseting after removing a textblock
Added cuiprinttree command along with cuidraworder
## 0.2.3.0
Made CUITextInput, CUITickBox and CUISlider use commands and consume data
Added Gap to CUIVerticalList
Made OnAnyCommand,OnAnyData,OnConsume events instead of delegates
added ReflectCommands and RetranslateCommands props, so you could define in xml that a wrapper should sync state between its children
Setting a Palette prop now won't automatically set palette for all children because it doesn't work as intended on deserialized components, use DeepPalette instead
CUISlider now have Range and Precision
CUI.OnPauseMenuToggled will now trigger even when resume button in pause menu is pressed
You can no just set CUIPalette.DefaultPalette before CUI.Initialize instead of manually loading it
Palettes now remember their BaseColor so you could replicate them
Added more useless CUIPrefabs, i think they are too specific and need some redesign, perhaps i need to create some builder
Added FloatRange alongside with IntRange
fixed crash in KeyboardDispatcher_set_Subscriber_Replace in compiled mods
## 0.2.2.1
Minor stuff: multibutton dispatches the command, CUIPages resizes page to [0,0,1,1], maincomponent flatten tree is a bit more thread safe
Added IRefreshable interface and CUIComponent.CascadeRefresh
CascadeRefresh will recursivelly call Refresh on every child that implements IRefreshable
## 0.2.2.0
Added to CUI.cs
```
using System.Runtime.CompilerServices;
[assembly: IgnoresAccessChecksTo("Barotrauma")]
[assembly: IgnoresAccessChecksTo("DedicatedServer")]
[assembly: IgnoresAccessChecksTo("BarotraumaCore")]
```
So it could be compiled
#### Temporary solution to pathing:
Now mod won't automatially find its folders
If you want to use lua you need to set CUI.ModDir to the mod folder path
Also you need to place Assets folder with CUI stuff somewhere in your mod and set CUI.AssetsPath to it
You can rename it, just set the path
All this needs to be done before CUI.Initialize()
## 0.2.1.0
Dried tree building methods, added tests for them
Added insert method along with append and prepend, unlike List.Insert it won't throw on "index out of bounds"
## 0.2.0.1
.nojekyll moment
## 0.2.0.0
Reworked CUIPalette, and CUICommand, check docs
Reworked border, added separate borders for each side, border sprite, outline
Changed how zindex is calculated, now every next child will have higher zindex -> everything in one frame will be above or below everything in the other
optimized CUITextBlock measurements, added some validation to CUITextInput
Added CUIPresets with... presets. Which you can use to reduce boilerplate code
Made more stuff parsable and serializble
And tons of other things i'm too lazy to write down, compare commits if you're curious
## 0.1.0.0
You can now use relative paths for sprite textures
You can set CUI.PGNAssets to the folder with your assets, CUI will also search for textures there
Reworked MasterColorOpaque, it now just uses base color alpha
Synced vertical and horizontal lists, added ScrollSpeed
OnUpdate event now invoked before layout calcs, Also added OnLayoutUpdated event, it's invoked before recalc of children layouts
"Fixed" imprecise rects that e.g. caused sprite overlap and gaps
Added CrossRelative prop, it's like Relative but values are relative to the oposite value, e.g. CrossRelative.Width = Host.Real.Height, convinient for making square things
Reworked slider component
DragHandle can now DragRelative to the parent
#### Serialization
Added BreakSerialization prop, if true serrialization will stop on this component
Added LoadSelfFromFile, SaveToTheSamePath methods
Added Hydrate method, it will run right after deserizlization, allowing you to access children with Get<> and e.g. save them to local vars
Added SaveAfterLoad prop, it's mostly for serialization debugging
Added more Custom ToString and parsed methods to CUIExtensions, Added native enum serialization, Vector2 and Rectangle is now serialized into [ x, y ], be carefull
Sprite is now fully serializable
## 0.0.5.1
Added "[CUISerializable] public string Command { get; set; }"" to CUIButton so you could define command that is called on click in xml
Splitted MasterColor into MasterColor and MasterColorOpaque
CUITextBlock RealTextSize is now rounded because 2.199999 is prone to floating-point errors
Added MasterColor to CUIToggleButton
Buttons now will folow a pattern: if state is changed by user input then it'll invoke StateChanged event
If state is changed from code then it won't invoke the event
When you change state from code you already know about that so you don't need an event
And on the other side if event is always fired it will create un-untangleable loops when you try to sync state between two buttons
Fixed CUIComponent.Get< T > method, i forgot to add it to docs, it gives you memorized component by its name, so it's same as frame["header"], but it can also give you nested components like that `Header = Frame.Get<CUIHorizontalList>("layout.content.header");`
Exposed ResizeHandles, you can hide them separately
Fixed crash when serializing a Vector2, added more try-catches and warnings there
Fixed Sprites in CUI.png being 33x33, i just created a wrong first rectangle and then copy-pasted it, guh
Added sprites to resize handles, and gradient sprite that's not used yet
Added `public SpriteEffects Effects;` to CUISprite, it controls texture flipping
More params in CUITextureManager.GetSprite
## 0.0.5.0
Added Styles
Every component has a Style and every Type has a DefaultStyle
Styles are observable dicts with pairs { "prop name", "prop value" } and can be used to assign any parsable string to any prop or reference some value from CUIPalette
## 0.0.4.0
Added CUICanvas.Render(Action< SpriteBatch > renderFunc) method that allows you to render anything you want onto the canvas texture
## 0.0.3.0
Added Changelog.md :BaroDev:
Added CUI.TopMain, it's the second CUIMainComponent which exists above vanilla GUI
Added printsprites command, it prints styles from GUIStyle.ComponentStyles
More fabric methods for CUISprite

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace QICrabUI
{
/// <summary>
/// A button
/// It's derived from CUITextBlock and has all its props
/// </summary>
public class CUIButton : CUITextBlock
{
[CUISerializable]
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
[CUISerializable] public Color DisabledColor { get; set; }
[CUISerializable] public Color InactiveColor { get; set; }
[CUISerializable] public Color MouseOverColor { get; set; }
[CUISerializable] public Color MousePressedColor { get; set; }
[CUISerializable] public bool AutoUpdateColor { get; set; } = true;
/// <summary>
/// Convenient prop to set all colors at once
/// </summary>
public Color MasterColor
{
set
{
InactiveColor = value.Multiply(0.7f);
MouseOverColor = value.Multiply(0.9f);
MousePressedColor = value;
DetermineColor();
}
}
public Color MasterColorOpaque
{
set
{
InactiveColor = new Color((int)(value.R * 0.7f), (int)(value.G * 0.7f), (int)(value.B * 0.7f), value.A);
MouseOverColor = new Color((int)(value.R * 0.9f), (int)(value.G * 0.9f), (int)(value.B * 0.9f), value.A);
MousePressedColor = value;
DetermineColor();
}
}
/// <summary>
/// BackgroundColor is used in base.Draw, but here it's calculated from colors above
/// So it's not a prop anymore, and i don't want to serialize it
/// </summary>
public new Color BackgroundColor
{
get => CUIProps.BackgroundColor.Value;
set => CUIProps.BackgroundColor.SetValue(value);
}
public void DetermineColor()
{
if (!AutoUpdateColor) return;
if (Disabled)
{
BackgroundColor = DisabledColor;
}
else
{
BackgroundColor = InactiveColor;
if (MouseOver) BackgroundColor = MouseOverColor;
if (MousePressed) BackgroundColor = MousePressedColor;
}
}
public override void Draw(SpriteBatch spriteBatch)
{
//DetermineColor();
base.Draw(spriteBatch);
}
public CUIButton() : base()
{
Text = "CUIButton";
ConsumeMouseClicks = true;
ConsumeDragAndDrop = true;
ConsumeSwipe = true;
OnMouseDown += (e) =>
{
if (!Disabled)
{
SoundPlayer.PlayUISound(ClickSound);
if (Command != null && Command != "")
{
DispatchUp(new CUICommand(Command));
}
}
};
OnMouseOff += (e) => DetermineColor();
OnMouseOn += (e) => DetermineColor();
OnStyleApplied += DetermineColor;
DetermineColor();
}
public CUIButton(string text) : this()
{
Text = text;
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// Allows you to manipulate pixel data of its texture
/// </summary>
public class CUICanvas : CUIComponent, IDisposable
{
public Color[] Data;
public RenderTarget2D Texture;
/// <summary>
/// Size of the internal texture
/// Will automatically resize the texture and data array of set
/// </summary>
public virtual Point Size
{
get => new Point(Texture.Width, Texture.Height);
set
{
if (value.X == Texture?.Width && value.Y == Texture?.Height) return;
RenderTarget2D oldTexture = Texture;
Texture = new RenderTarget2D(GameMain.Instance.GraphicsDevice, value.X, value.Y);
Data = new Color[Texture.Width * Texture.Height];
BackgroundSprite = new CUISprite(Texture);
oldTexture?.Dispose();
}
}
public void Clear(Color? color = null)
{
Color cl = color ?? Color.Transparent;
for (int i = 0; i < Data.Length; i++)
{
Data[i] = cl;
}
SetData();
}
public Color GetPixel(int x, int y)
{
return Data[y * Texture.Width + x];
}
public void SetPixel(int x, int y, Color cl)
{
Data[y * Texture.Width + x] = cl;
}
/// <summary>
/// Call this method to transfer values from Data array into texture
/// </summary>
public void SetData()
{
Texture.SetData<Color>(Data);
}
/// <summary>
/// Uses renderFunc to render stuff directy onto Canvas.Texture
/// You can for example use GUI "Draw" methods with provided spriteBatch
/// </summary>
/// <param name="renderFunc"> Action<SpriteBatch> where you can draw whatever you want </param>
public void Render(Action<SpriteBatch> renderFunc)
{
GameMain.Instance.GraphicsDevice.SetRenderTarget(Texture);
//TODO save and restore scissor rect
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
renderFunc(spriteBatch);
spriteBatch.End();
GameMain.Instance.GraphicsDevice.SetRenderTarget(null);
}
public SpriteBatch spriteBatch;
public CUICanvas(int x, int y) : base()
{
Size = new Point(x, y);
spriteBatch = new SpriteBatch(GameMain.Instance.GraphicsDevice);
}
public CUICanvas() : this(100, 100) { }
public override void CleanUp()
{
Texture?.Dispose();
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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,
};
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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; }
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,244 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using System.Xml.Linq;
namespace QICrabUI
{
/// <summary>
/// Swipable and zoomable plane
/// Allows you to place components in a plane
/// and connect them with lines like a graph or scheme
/// </summary>
public class CUIMap : CUIComponent
{
#region CUIMapLink
#endregion
public class CUIMapLink
{
internal static void InitStatic()
{
CUI.OnInit += () => Default = new CUIMapLink(null, null);
CUI.OnDispose += () => Default = null;
}
public static CUIMapLink Default;
public CUIComponent Start;
public CUIComponent End;
//TODO all this crap wasn't designed for nested AKA
public string StartAKA;
public string EndAKA;
public float LineWidth;
public Color LineColor;
public XElement ToXML()
{
XElement connection = new XElement("Connection");
if (LineWidth != Default.LineWidth)
{
connection.SetAttributeValue("LineWidth", LineWidth);
}
connection.SetAttributeValue("Start", StartAKA ?? "");
connection.SetAttributeValue("End", EndAKA ?? "");
return connection;
}
public CUIMapLink(CUIComponent start, CUIComponent end, Color? lineColor = null, float lineWidth = 2f)
{
LineColor = lineColor ?? new Color(128, 128, 128);
LineWidth = lineWidth;
Start = start;
End = end;
StartAKA = start?.AKA;
EndAKA = end?.AKA;
}
}
#region LinksContainer
#endregion
public class LinksContainer : CUIComponent
{
public List<CUIMapLink> Connections = new List<CUIMapLink>();
public override void Draw(SpriteBatch spriteBatch)
{
base.Draw(spriteBatch);
foreach (CUIMapLink link in Connections)
{
Vector2 midPoint = new Vector2(link.End.Real.Center.X, link.Start.Real.Center.Y);
GUI.DrawLine(spriteBatch,
link.Start.Real.Center,
midPoint,
link.LineColor, width: link.LineWidth
);
GUI.DrawLine(spriteBatch,
midPoint,
link.End.Real.Center,
link.LineColor, width: link.LineWidth
);
}
}
public LinksContainer()
{
UnCullable = true;
BackgroundColor = Color.Transparent;
Border.Color = Color.Transparent;
}
}
#region CUIMap
#endregion
public LinksContainer linksContainer;
public List<CUIMapLink> Connections => linksContainer.Connections;
public CUIComponent Add(CUIComponent c) => Append(c, c.AKA);
public CUIComponent Connect(CUIComponent startComponent, CUIComponent endComponent, Color? color = null)
{
if (startComponent != null && endComponent != null)
{
if (color == null && (!startComponent.Disabled || !endComponent.Disabled)) color = new Color(0, 0, 255);
linksContainer.Connections.Add(new CUIMapLink(startComponent, endComponent, color));
}
return startComponent;
}
public CUIComponent Connect(CUIComponent startComponent, int end = -2, Color? color = null)
{
end = MathUtils.PositiveModulo(end, Children.Count);
CUIComponent endComponent = Children.ElementAtOrDefault(end);
return Connect(startComponent, endComponent, color);
}
//TODO DRY
public CUIComponent Connect(string start, string end, Color? color = null)
{
CUIComponent startComponent = this[start];
CUIComponent endComponent = this[end];
if (startComponent != null && endComponent != null)
{
if (color == null && (!startComponent.Disabled || !endComponent.Disabled)) color = new Color(0, 0, 255);
linksContainer.Connections.Add(new CUIMapLink(startComponent, endComponent, color)
{
StartAKA = start,
EndAKA = end,
});
}
return startComponent;
}
public CUIComponent Connect(int start, int end, Color? color = null)
{
start = MathUtils.PositiveModulo(start, Children.Count);
end = MathUtils.PositiveModulo(end, Children.Count);
CUIComponent startComponent = Children.ElementAtOrDefault(start);
CUIComponent endComponent = Children.ElementAtOrDefault(end);
return Connect(startComponent, endComponent, color);
}
public CUIComponent ConnectTo(CUIComponent Host, params CUIComponent[] children)
{
foreach (CUIComponent child in children) { Connect(Host, child); }
return Host;
}
public override XElement ToXML(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
{
Type type = GetType();
XElement element = new XElement(type.Name);
PackProps(element, propAttribute);
XElement connections = new XElement("Connections");
element.Add(connections);
foreach (CUIMapLink link in Connections)
{
connections.Add(link.ToXML());
}
XElement children = new XElement("Children");
element.Add(children);
foreach (CUIComponent child in Children)
{
if (child == linksContainer) continue;
children.Add(child.ToXML());
}
return element;
}
public override void FromXML(XElement element, string baseFolder = null)
{
foreach (XElement childElement in element.Element("Children").Elements())
{
Type childType = CUIReflection.GetComponentTypeByName(childElement.Name.ToString());
if (childType == null) continue;
CUIComponent child = (CUIComponent)Activator.CreateInstance(childType);
child.FromXML(childElement);
this.Append(child, child.AKA);
}
foreach (XElement link in element.Element("Connections").Elements())
{
CUIComponent startComponent = this[link.Attribute("Start").Value];
CUIComponent endComponent = this[link.Attribute("End").Value];
if (startComponent == null || endComponent == null)
{
CUIDebug.Error("startComponent == null || endComponent == null");
continue;
}
Connect(link.Attribute("Start").Value, link.Attribute("End").Value);
}
//TODO: think, this is potentially very bugged,
// Some props might need to be assigned before children, and some after
ExtractProps(element);
}
public CUIMap() : base()
{
Swipeable = true;
ConsumeMouseClicks = true;
CullChildren = true;
BackgroundColor = Color.Transparent;
//without container links won't be culled
//TODO linksContainer should be special and not just first child
this["links"] = linksContainer = new LinksContainer();
OnScroll += (m) =>
{
CUIProps.ChildrenOffset.SetValue(
ChildrenOffset.Zoom(
m.MousePosition - Real.Position,
(-m.Scroll / 500f)
)
);
};
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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(),
};
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
};
}
}
}

View File

@@ -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);
}
};
}
}
}

View File

@@ -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;
};
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -0,0 +1,95 @@
#define CUIDEBUG
// #define SHOWPERF
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.IO;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
public static class CUIDebug
{
public static bool PrintKeys;
#if !CUIDEBUG
[Conditional("DONT")]
#endif
public static void Log(object msg, Color? cl = null)
{
if (!CUI.Debug) return;
cl ??= Color.Yellow;
LuaCsLogger.LogMessage($"{msg ?? "null"}", cl * 0.8f, cl);
}
#if !CUIDEBUG
[Conditional("DONT")]
#endif
public static void Info(object msg, Color? cl = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
{
if (!CUI.Debug) return;
cl ??= Color.Cyan;
var fi = new FileInfo(source);
CUI.Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", cl * 0.5f);
CUI.Log(msg, cl);
}
#if !CUIDEBUG
[Conditional("DONT")]
#endif
public static void Error(object msg, Color? cl = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
{
if (!CUI.Debug) return;
cl ??= Color.Orange;
var fi = new FileInfo(source);
CUI.Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", cl * 0.5f);
CUI.Log(msg, cl);
}
#if !CUIDEBUG
[Conditional("DONT")]
#endif
public static void Capture(CUIComponent host, CUIComponent target, string method, string sprop, string tprop, string value)
{
if (target == null || target.IgnoreDebug || !target.Debug) return;
//CUI.Log($"{host} {target} {method} {sprop} {tprop} {value}");
CUIDebugWindow.Main?.Capture(new CUIDebugEvent(host, target, method, sprop, tprop, value));
}
#if !CUIDEBUG
[Conditional("DONT")]
#endif
public static void Flush() => CUIDebugWindow.Main?.Flush();
// public static int CUIShowperfCategory = 1000;
// #if (!SHOWPERF || !CUIDEBUG)
// [Conditional("DONT")]
// #endif
// public static void CaptureTicks(double ticks, string name, int hash) => ShowPerfExtensions.Plugin.CaptureTicks(ticks, CUIShowperfCategory, name, hash);
// #if (!SHOWPERF || !CUIDEBUG)
// [Conditional("DONT")]
// #endif
// public static void CaptureTicks(double ticks, string name) => ShowPerfExtensions.Plugin.CaptureTicks(ticks, CUIShowperfCategory, name);
// #if (!SHOWPERF || !CUIDEBUG)
// [Conditional("DONT")]
// #endif
// public static void EnsureCategory() => ShowPerfExtensions.Plugin.EnsureCategory(CUIShowperfCategory);
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,223 @@
#define CUIDEBUG
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.IO;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
public class CUIDebugWindow : CUIFrame
{
public static CUIDebugWindow Main;
public CUIVerticalList EventsComponent;
public CUIVerticalList DebugIDsComponent;
public CUIPages Pages;
public CUIMultiButton PickIDButton;
public List<CUIDebugEventComponent> Events = new List<CUIDebugEventComponent>();
public int target;
public bool Loop { get; set; } = true;
public void Capture(CUIDebugEvent e)
{
if (EventsComponent == null) return;
if (target > 200) return;
if (Events.Count < target + 1)
{
CUIDebugEventComponent ec = new CUIDebugEventComponent(e);
Events.Add(ec);
EventsComponent.Append(ec);
ec.OnMouseEnter += (m) => ec.Value.Target.DebugHighlight = true;
ec.OnMouseLeave += (m) => ec.Value.Target.DebugHighlight = false;
}
else
{
Events[target].Value = e;
}
target++;
}
public void Flush()
{
if (Loop) target = 0;
//Events.ForEach(e => e.Flush());
}
public void MakeIDList()
{
DebugIDsComponent.Visible = false;
DebugIDsComponent.RemoveAllChildren();
List<CUIComponent> l = new List<CUIComponent>();
if (CUI.Main is not null)
{
RunRecursiveOn(CUI.Main, (component) =>
{
if (!component.IgnoreDebug) l.Add(component);
});
}
foreach (CUIComponent c in l)
{
CUIToggleButton b = new CUIToggleButton(c.ToString())
{
State = c.Debug,
IgnoreDebug = true,
Style = new CUIStyle(){
{"TextAlign", "[0,0]"}
},
AddOnMouseDown = (m) =>
{
c.Debug = !c.Debug;
MakeIDList();
},
AddOnMouseEnter = (m) => c.DebugHighlight = true,
AddOnMouseLeave = (m) => c.DebugHighlight = false,
};
DebugIDsComponent.Append(b);
}
DebugIDsComponent.Visible = true;
l.Clear();
}
public CUIDebugWindow() : base()
{
this.ZIndex = 1000;
this.Layout = new CUILayoutVerticalList();
this["handle"] = new CUIComponent()
{
Absolute = new CUINullRect(null, null, null, 20),
};
this["handle"]["closebutton"] = new CUIButton("X")
{
Anchor = new Vector2(1, 0.5f),
Style = new CUIStyle(){
{"MasterColor", "Red"}
},
AddOnMouseDown = (e) =>
{
CUIDebugWindow.Close();
},
};
this["controls"] = new CUIComponent()
{
FitContent = new CUIBool2(false, true),
};
this["controls"]["loop"] = new CUIToggleButton("loop")
{
Relative = new CUINullRect(0, 0, 0.5f, null),
AddOnStateChange = (state) =>
{
Loop = state;
Events?.Clear();
EventsComponent?.RemoveAllChildren();
},
State = Loop,
};
this["controls"].Append(PickIDButton = new CUIMultiButton()
{
Relative = new CUINullRect(0.5f, 0, 0.5f, null),
Style = new CUIStyle(){
{"InactiveColor", "0,0,0,128"},
{"MousePressedColor", "0,255,255,64"}
},
ConsumeDragAndDrop = false,
Options = new string[]{
"Debug events", "Debugged components"
}
});
Append(Pages = new CUIPages()
{
FillEmptySpace = new CUIBool2(false, true),
Style = new CUIStyle(){
{"BackgroundColor", "0,0,32,128"}
},
IgnoreDebug = true,
});
EventsComponent = new CUIVerticalList()
{
Relative = new CUINullRect(0, 0, 1, 1),
Scrollable = true,
IgnoreDebug = true,
};
DebugIDsComponent = new CUIVerticalList()
{
Relative = new CUINullRect(0, 0, 1, 1),
Scrollable = true,
IgnoreDebug = true,
};
PickIDButton.OnSelect += (s) =>
{
if (PickIDButton.SelectedIndex == 0)
{
MakeIDList();
Pages.Open(EventsComponent);
}
else Pages.Open(DebugIDsComponent);
};
PickIDButton.Select(0);
this["controls"].Get<CUIToggleButton>("loop").State = true;
IgnoreDebug = true;
}
public static CUIDebugWindow Open()
{
if (CUI.Main == null) return null;
CUIDebugWindow w = new CUIDebugWindow()
{
Absolute = new CUINullRect(10, 370, 500, 370),
};
CUI.Main.Append(w);
CUIDebugWindow.Main = w;
CUI.Main.OnTreeChanged += () => w.MakeIDList();
return w;
}
public static void Close()
{
if (CUIDebugWindow.Main == null) return;
CUIDebugWindow.Main.RemoveSelf();
CUIDebugWindow.Main.Revealed = false;
CUIDebugWindow.Main = null;
}
public CUIDebugWindow(float? x = null, float? y = null, float? w = null, float? h = null) : this()
{
Relative = new CUINullRect(x, y, w, h);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// Containing a snapshot of current mouse and keyboard state
/// </summary>
public class CUIInput
{
public static double DoubleClickInterval = 0.2;
public static float ScrollSpeed = 0.6f;
public MouseState Mouse;
public bool MouseDown;
public bool DoubleClick;
public bool MouseUp;
public bool MouseHeld;
public float Scroll;
public bool Scrolled;
public Vector2 MousePosition;
public Vector2 MousePositionDif;
public bool MouseMoved;
//TODO split into sh mouse and sh keyboard
public bool SomethingHappened;
//HACK rethink, this is too hacky
public bool ClickConsumed;
public KeyboardState Keyboard;
public Keys[] HeldKeys = new Keys[0];
public Keys[] PressedKeys = new Keys[0];
public Keys[] UnpressedKeys = new Keys[0];
public bool SomeKeyHeld;
public bool SomeKeyPressed;
public bool SomeKeyUnpressed;
public TextInputEventArgs[] WindowTextInputEvents;
public TextInputEventArgs[] WindowKeyDownEvents;
public bool SomeWindowEvents;
//-------------- private stuff
private double PrevMouseDownTiming;
private int PrevScrollWheelValue;
private MouseState PrevMouseState;
private Vector2 PrevMousePosition;
private Keys[] PrevHeldKeys = new Keys[0];
private Queue<TextInputEventArgs> WindowTextInputQueue = new Queue<TextInputEventArgs>(10);
private Queue<TextInputEventArgs> WindowKeyDownQueue = new Queue<TextInputEventArgs>(10);
//HACK super hacky solution to block input from one CUIMainComponent to another
public bool MouseInputHandled { get; set; }
public void Scan(double totalTime)
{
MouseInputHandled = false;
ScanMouse(totalTime);
ScanKeyboard(totalTime);
}
private void ScanMouse(double totalTime)
{
ClickConsumed = false;
Mouse = Microsoft.Xna.Framework.Input.Mouse.GetState();
MouseDown = PrevMouseState.LeftButton == ButtonState.Released && Mouse.LeftButton == ButtonState.Pressed;
MouseUp = PrevMouseState.LeftButton == ButtonState.Pressed && Mouse.LeftButton == ButtonState.Released;
MouseHeld = Mouse.LeftButton == ButtonState.Pressed;
PrevMousePosition = MousePosition;
MousePosition = new Vector2(Mouse.Position.X, Mouse.Position.Y);
MousePositionDif = MousePosition - PrevMousePosition;
MouseMoved = MousePositionDif != Vector2.Zero;
Scroll = (Mouse.ScrollWheelValue - PrevScrollWheelValue) * ScrollSpeed;
PrevScrollWheelValue = Mouse.ScrollWheelValue;
Scrolled = Scroll != 0;
DoubleClick = false;
if (MouseDown)
{
if (totalTime - PrevMouseDownTiming < DoubleClickInterval)
{
DoubleClick = true;
}
PrevMouseDownTiming = totalTime;
}
SomethingHappened = MouseHeld || MouseUp || MouseDown || MouseMoved || Scrolled;
PrevMouseState = Mouse;
}
private void ScanKeyboard(double totalTime)
{
Keyboard = Microsoft.Xna.Framework.Input.Keyboard.GetState();
HeldKeys = Keyboard.GetPressedKeys();
SomeKeyHeld = HeldKeys.Length > 0;
PressedKeys = HeldKeys.Except(PrevHeldKeys).ToArray();
UnpressedKeys = PrevHeldKeys.Except(HeldKeys).ToArray();
SomeKeyPressed = PressedKeys.Length > 0;
SomeKeyUnpressed = UnpressedKeys.Length > 0;
PrevHeldKeys = HeldKeys;
WindowTextInputEvents = WindowTextInputQueue.ToArray();
WindowTextInputQueue.Clear();
WindowKeyDownEvents = WindowKeyDownQueue.ToArray();
WindowKeyDownQueue.Clear();
SomeWindowEvents = WindowTextInputEvents.Length > 0 || WindowKeyDownEvents.Length > 0;
}
public CUIInput()
{
CUI.OnWindowKeyDown += (e) => WindowKeyDownQueue.Enqueue(e);
CUI.OnWindowTextInput += (e) => WindowTextInputQueue.Enqueue(e);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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
{
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.IO;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using HarmonyLib;
using EventInput;
namespace QICrabUI
{
public partial class CUI
{
public static void CheckOtherPatches(string msg = "")
{
CUI.Log(msg);
CUI.Log($"Harmony.GetAllPatchedMethods:", Color.Lime);
foreach (MethodBase mb in Harmony.GetAllPatchedMethods())
{
Patches patches = Harmony.GetPatchInfo(mb);
if (patches.Prefixes.Count() > 0 || patches.Postfixes.Count() > 0)
{
CUI.Log($"{mb.DeclaringType}.{mb.Name}:");
if (patches.Prefixes.Count() > 0)
{
CUI.Log($" Prefixes:");
foreach (Patch patch in patches.Prefixes) { CUI.Log($" {patch.owner}"); }
}
if (patches.Postfixes.Count() > 0)
{
CUI.Log($" Postfixes:");
foreach (Patch patch in patches.Postfixes) { CUI.Log($" {patch.owner}"); }
}
}
}
}
public static void CheckPatches(string typeName, string methodName)
{
CUI.Log($"Harmony.GetAllPatchedMethods:", Color.Lime);
foreach (MethodBase mb in Harmony.GetAllPatchedMethods())
{
if (
!string.Equals(typeName, mb.DeclaringType.Name, StringComparison.OrdinalIgnoreCase) ||
!string.Equals(methodName, mb.Name, StringComparison.OrdinalIgnoreCase)
) continue;
Patches patches = Harmony.GetPatchInfo(mb);
if (patches.Prefixes.Count() > 0 || patches.Postfixes.Count() > 0)
{
CUI.Log($"{mb.DeclaringType}.{mb.Name}:");
if (patches.Prefixes.Count() > 0)
{
CUI.Log($" Prefixes:");
foreach (Patch patch in patches.Prefixes) { CUI.Log($" {patch.owner}"); }
}
if (patches.Postfixes.Count() > 0)
{
CUI.Log($" Postfixes:");
foreach (Patch patch in patches.Postfixes) { CUI.Log($" {patch.owner}"); }
}
}
}
}
private static void PatchAll()
{
GameMain.LuaCs.Hook.Add("GUI_Draw_Prefix", CUIHookID, (object[] args) =>
{
GUI_Draw_Prefix((SpriteBatch)args.ElementAtOrDefault(0));
return null;
});
GameMain.LuaCs.Hook.Add("GUI_DrawCursor_Prefix", CUIHookID, (object[] args) =>
{
GUI_DrawCursor_Prefix((SpriteBatch)args.ElementAtOrDefault(0));
return null;
});
GameMain.LuaCs.Hook.Add("think", CUIHookID, (object[] args) =>
{
CUIUpdateMouseOn();
CUIUpdate(Timing.TotalTime);
return null;
});
// this hook seems to do nothing
// GameMain.LuaCs.Hook.Add("Camera_MoveCamera_Prefix", CUIHookID, (object[] args) =>
// {
// return Camera_MoveCamera_Prefix(); ;
// });
GameMain.LuaCs.Hook.Add("KeyboardDispatcher_set_Subscriber_Prefix", CUIHookID, (object[] args) =>
{
KeyboardDispatcher_set_Subscriber_Prefix(
(KeyboardDispatcher)args.ElementAtOrDefault(0),
(IKeyboardSubscriber)args.ElementAtOrDefault(1)
);
return null;
});
GameMain.LuaCs.Hook.Add("GUI_InputBlockingMenuOpen_Postfix", CUIHookID, (object[] args) =>
{
return GUI_InputBlockingMenuOpen_Postfix();
});
GameMain.LuaCs.Hook.Add("GUI_TogglePauseMenu_Postfix", CUIHookID, (object[] args) =>
{
GUI_TogglePauseMenu_Postfix();
return null;
});
}
private static void GameMain_Update_Postfix(GameTime gameTime)
{
CUIUpdate(gameTime.TotalGameTime.TotalSeconds);
}
private static void CUIUpdate(double time)
{
if (Main == null) CUI.Error($"CUIUpdate: CUI.Main in {HookIdentifier} was null, tell the dev", 20);
try
{
CUIAnimation.UpdateAllAnimations(time);
CUI.Input?.Scan(time);
TopMain?.Update(time);
Main?.Update(time);
}
catch (Exception e)
{
CUI.Warning($"CUI: {e}");
}
}
private static void GUI_Draw_Prefix(SpriteBatch spriteBatch)
{
try { Main?.Draw(spriteBatch); }
catch (Exception e) { CUI.Warning($"CUI: {e}"); }
}
private static void GUI_DrawCursor_Prefix(SpriteBatch spriteBatch)
{
try { TopMain?.Draw(spriteBatch); }
catch (Exception e) { CUI.Warning($"CUI: {e}"); }
}
private static void GUI_UpdateMouseOn_Postfix(ref GUIComponent __result)
{
CUIUpdateMouseOn();
}
private static void CUIUpdateMouseOn()
{
if (Main == null) CUI.Error($"CUIUpdateMouseOn: CUI.Main in {HookIdentifier} was null, tell the dev", 20);
if (GUI.MouseOn == null && Main != null && Main.MouseOn != null && Main.MouseOn != Main) GUI.MouseOn = CUIComponent.dummyComponent;
if (TopMain != null && TopMain.MouseOn != null && TopMain.MouseOn != TopMain) GUI.MouseOn = CUIComponent.dummyComponent;
}
private static Dictionary<string, bool> Camera_MoveCamera_Prefix()
{
if (GUI.MouseOn != CUIComponent.dummyComponent) return null;
return new Dictionary<string, bool>()
{
["allowZoom"] = false,
};
}
private static void KeyboardDispatcher_set_Subscriber_Prefix(KeyboardDispatcher __instance, IKeyboardSubscriber value)
{
FocusResolver?.OnVanillaIKeyboardSubscriberSet(value);
}
public static bool GUI_InputBlockingMenuOpen_Postfix()
{
return CUI.InputBlockingMenuOpen;
}
public static void GUI_TogglePauseMenu_Postfix()
{
try
{
if (GUI.PauseMenu != null)
{
GUIFrame frame = GUI.PauseMenu;
GUIComponent pauseMenuInner = frame.GetChild(1);
GUIComponent list = frame.GetChild(1).GetChild(0);
GUIButton resumeButton = (GUIButton)list.GetChild(0);
GUIButton.OnClickedHandler oldHandler = resumeButton.OnClicked;
resumeButton.OnClicked = (GUIButton button, object obj) =>
{
bool guh = oldHandler(button, obj);
CUI.InvokeOnPauseMenuToggled();
return guh;
};
}
}
catch (Exception e) { CUI.Warning(e); }
CUI.InvokeOnPauseMenuToggled();
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma;
using HarmonyLib;
using Microsoft.Xna.Framework;
using System.IO;
namespace QICrabUI
{
public partial class CUI
{
//Idk, not very usefull
/// <summary>
/// Just an experimant
/// Creates empty CUIComponent from class name
/// </summary>
/// <param name="componentName"></param>
/// <returns></returns>
public static CUIComponent Create(string componentName)
{
return (CUIComponent)Activator.CreateInstance(CUIReflection.GetComponentTypeByName(componentName));
}
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.IO;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using HarmonyLib;
namespace QICrabUI
{
public partial class CUI
{
internal static List<DebugConsole.Command> AddedCommands = new List<DebugConsole.Command>();
internal static void AddCommands()
{
AddedCommands.Add(new DebugConsole.Command("cuidebug", "", CUIDebug_Command));
AddedCommands.Add(new DebugConsole.Command("cuicreatepalette", "cuicreatepalette name frontcolor [backcolor]", CUICreatePalette_Command));
AddedCommands.Add(new DebugConsole.Command("cuimg", "", CUIMG_Command));
AddedCommands.Add(new DebugConsole.Command("cuidraworder", "", CUIDrawOrder_Command));
AddedCommands.Add(new DebugConsole.Command("cuiprinttree", "", CUIPrintTree_Command));
AddedCommands.Add(new DebugConsole.Command("printsprites", "", PrintSprites_Command));
AddedCommands.Add(new DebugConsole.Command("printkeys", "", PrintSprites_Command));
AddedCommands.Add(new DebugConsole.Command("cuipalette", "load palette as primary", Palette_Command, () => new string[][] { CUIPalette.LoadedPalettes.Keys.ToArray() }));
AddedCommands.Add(new DebugConsole.Command("cuipalettedemo", "", PaletteDemo_Command));
AddedCommands.Add(new DebugConsole.Command("cuicreatepaletteset", "name primaty secondary tertiary quaternary", CUICreatePaletteSet_Command, () => new string[][] {
new string[]{},
CUIPalette.LoadedPalettes.Keys.ToArray(),
CUIPalette.LoadedPalettes.Keys.ToArray(),
CUIPalette.LoadedPalettes.Keys.ToArray(),
CUIPalette.LoadedPalettes.Keys.ToArray(),
}));
AddedCommands.Add(new DebugConsole.Command("cuiloadpaletteset", "", CUILoadPaletteSet_Command));
AddedCommands.Add(new DebugConsole.Command("cuicreateluatypesfile", "", CUICreateLuaTypesFile_Command));
DebugConsole.Commands.InsertRange(0, AddedCommands);
}
public static void CUICreateLuaTypesFile_Command(string[] args)
{
CUI.LuaRegistrar.ConstructLuaStaticsFile();
}
public static void CUIDebug_Command(string[] args)
{
if (CUIDebugWindow.Main == null)
{
CUIDebugWindow.Open();
}
else
{
CUIDebugWindow.Close();
}
}
public static void CUIDrawOrder_Command(string[] args)
{
foreach (CUIComponent c in CUI.Main.Flat)
{
CUI.Log(c);
}
}
public static void CUIPrintTree_Command(string[] args)
{
CUI.Main?.PrintTree();
CUI.TopMain?.PrintTree();
}
public static void CUICreatePalette_Command(string[] args)
{
string name = args.ElementAtOrDefault(0);
Color colorA = CUIExtensions.ParseColor((args.ElementAtOrDefault(1) ?? "white"));
Color colorB = CUIExtensions.ParseColor((args.ElementAtOrDefault(2) ?? "black"));
CUIPalette palette = CUIPalette.CreatePaletteFromColors(name, colorA, colorB);
CUIPalette.Primary = palette;
}
public static void CUICreatePaletteSet_Command(string[] args)
{
CUIPalette.SaveSet(
args.ElementAtOrDefault(0),
args.ElementAtOrDefault(1),
args.ElementAtOrDefault(2),
args.ElementAtOrDefault(3),
args.ElementAtOrDefault(4)
);
}
public static void CUILoadPaletteSet_Command(string[] args)
{
CUIPalette.LoadSet(Path.Combine(CUIPalette.PaletteSetsPath, args.ElementAtOrDefault(0)));
}
public static void CUIMG_Command(string[] args) => CUIMagnifyingGlass.ToggleEquip();
public static void PrintSprites_Command(string[] args)
{
foreach (GUIComponentStyle style in GUIStyle.ComponentStyles)
{
CUI.Log($"{style.Name} {style.Sprites.Count}");
}
}
public static void PrintKeysCommand(string[] args)
{
CUIDebug.PrintKeys = !CUIDebug.PrintKeys;
if (CUIDebug.PrintKeys)
{
var values = typeof(Keys).GetEnumValues();
foreach (var v in values)
{
Log($"{(int)v} {v}");
}
Log("---------------------------");
}
}
public static void PaletteDemo_Command(string[] args)
{
try { CUIPalette.PaletteDemo(); } catch (Exception e) { CUI.Warning(e); }
}
public static void Palette_Command(string[] args)
{
try
{
CUIPalette palette = CUIPalette.LoadedPalettes?.GetValueOrDefault(args.ElementAtOrDefault(0) ?? "");
if (palette != null) CUIPalette.Primary = palette;
}
catch (Exception e) { CUI.Warning(e); }
}
internal static void RemoveCommands()
{
AddedCommands.ForEach(c => DebugConsole.Commands.Remove(c));
AddedCommands.Clear();
}
// public static void PermitCommands(Identifier command, ref bool __result)
// {
// if (AddedCommands.Any(c => c.Names.Contains(command.Value))) __result = true;
// }
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
namespace QICrabUI
{
public partial class CUI
{
public const float Pi2 = (float)(Math.PI / 2.0);
public static SamplerState NoSmoothing = new SamplerState()
{
Filter = TextureFilter.Point,
AddressU = TextureAddressMode.Clamp,
AddressV = TextureAddressMode.Clamp,
AddressW = TextureAddressMode.Clamp,
BorderColor = Color.White,
MaxAnisotropy = 4,
MaxMipLevel = 0,
MipMapLevelOfDetailBias = -0.8f,
ComparisonFunction = CompareFunction.Never,
FilterMode = TextureFilterMode.Default,
};
public static void DrawTexture(SpriteBatch sb, CUIRect cuirect, Color cl, Texture2D texture, float depth = 0.0f)
{
Rectangle sourceRect = new Rectangle(0, 0, (int)cuirect.Width, (int)cuirect.Height);
sb.Draw(texture, cuirect.Box, sourceRect, cl, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
}
public static void DrawRectangle(SpriteBatch sb, CUIRect cuirect, Color cl, CUISprite sprite, float depth = 0.0f)
{
Rectangle sourceRect = sprite.DrawMode switch
{
CUISpriteDrawMode.Resize => sprite.SourceRect,
CUISpriteDrawMode.Wrap => new Rectangle(0, 0, (int)cuirect.Width, (int)cuirect.Height),
CUISpriteDrawMode.Static => cuirect.Box,
CUISpriteDrawMode.StaticDeep => cuirect.Zoom(0.9f),
_ => sprite.SourceRect,
};
Rectangle rect = new Rectangle(
(int)(cuirect.Left + sprite.Offset.X * cuirect.Width),
(int)(cuirect.Top + sprite.Offset.Y * cuirect.Height),
(int)(cuirect.Width),
(int)(cuirect.Height)
);
//rect = cuirect.Box;
sb.Draw(sprite.Texture, rect, sourceRect, cl, sprite.Rotation, sprite.Origin, sprite.Effects, depth);
}
//TODO i can calculate those rects in advance
public static void DrawBorders(SpriteBatch sb, CUIComponent component, float depth = 0.0f)
{
Texture2D texture = component.BorderSprite.Texture;
Rectangle sourceRect = texture.Bounds;
Rectangle targetRect;
Color cl;
float rotation = 0.0f;
float thickness = 1.0f;
bool visible = false;
// Right
visible = component.RigthBorder?.Visible ?? component.Border.Visible;
thickness = component.RigthBorder?.Thickness ?? component.Border.Thickness;
cl = component.RigthBorder?.Color ?? component.Border.Color;
targetRect = CUIRect.CreateRect(
component.Real.Left + component.Real.Width,
component.Real.Top,
component.Real.Height,
thickness
);
sourceRect = CUIRect.CreateRect(
0, 0,
targetRect.Width, texture.Height
);
rotation = Pi2;
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.None, depth);
//Left
visible = component.LeftBorder?.Visible ?? component.Border.Visible;
thickness = component.LeftBorder?.Thickness ?? component.Border.Thickness;
cl = component.LeftBorder?.Color ?? component.Border.Color;
targetRect = CUIRect.CreateRect(
component.Real.Left + thickness,
component.Real.Top,
component.Real.Height,
thickness
);
sourceRect = CUIRect.CreateRect(
0, 0,
targetRect.Width, texture.Height
);
rotation = Pi2;
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.FlipVertically, depth);
//Top
visible = component.TopBorder?.Visible ?? component.Border.Visible;
thickness = component.TopBorder?.Thickness ?? component.Border.Thickness;
cl = component.TopBorder?.Color ?? component.Border.Color;
targetRect = CUIRect.CreateRect(
component.Real.Left,
component.Real.Top,
component.Real.Width,
thickness
);
sourceRect = CUIRect.CreateRect(
0, 0,
targetRect.Width, texture.Height
);
rotation = 0.0f;
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.None, depth);
//Bottom
visible = component.BottomBorder?.Visible ?? component.Border.Visible;
thickness = component.BottomBorder?.Thickness ?? component.Border.Thickness;
cl = component.BottomBorder?.Color ?? component.Border.Color;
targetRect = CUIRect.CreateRect(
component.Real.Left,
component.Real.Bottom - thickness,
component.Real.Width,
thickness
);
sourceRect = CUIRect.CreateRect(
0, 0,
targetRect.Width, texture.Height
);
rotation = 0;
sb.Draw(texture, targetRect, sourceRect, cl, rotation, Vector2.Zero, SpriteEffects.FlipVertically, depth);
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma;
using HarmonyLib;
using Microsoft.Xna.Framework;
using System.IO;
namespace QICrabUI
{
public partial class CUI
{
public static Dictionary<string, int> Errors = new();
public static void Error(object msg, int maxPrints = 1, bool silent = false)
{
string s = $"{msg}";
if (!Errors.ContainsKey(s)) Errors[s] = 1;
else Errors[s] = Errors[s] + 1;
if (silent) return;
if (Errors[s] <= maxPrints) Log($"CUI: {s} x{Errors[s]}", Color.Orange);
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.IO;
using System.Globalization;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using HarmonyLib;
namespace QICrabUI
{
[CUIInternal]
public static partial class CUIExtensions
{
public static int Fit(this int i, int bottom, int top) => Math.Max(bottom, Math.Min(i, top));
public static Vector2 Rotate(this Vector2 v, float angle) => Vector2.Transform(v, Matrix.CreateRotationZ(angle));
public static string SubstringSafe(this string s, int start)
{
try
{
int safeStart = start.Fit(0, s.Length);
return s.Substring(safeStart, s.Length - safeStart);
}
catch (Exception e)
{
CUI.Log($"SubstringSafe {e}");
return "";
}
}
public static string SubstringSafe(this string s, int start, int length)
{
int end = (start + length).Fit(0, s.Length);
int safeStart = start.Fit(0, s.Length);
int safeLength = end - safeStart;
try
{
return s.Substring(safeStart, safeLength);
}
catch (Exception e)
{
CUI.Log($"SubstringSafe {e.Message}\ns:\"{s}\" start: {start}->{safeStart} end: {end} length: {length}->{safeLength} ", Color.Orange);
return "";
}
}
public static Dictionary<string, string> ParseKVPairs(string raw)
{
Dictionary<string, string> props = new();
if (raw == null || raw == "") return props;
string content = raw.Split('{', '}')[1];
List<string> expressions = new();
int start = 0;
int end = 0;
int depth = 0;
for (int i = 0; i < content.Length; i++)
{
char c = content[i];
end = i;
if (c == '[' || c == '{') depth++;
if (c == ']' || c == '}') depth--;
if (depth <= 0 && c == ',')
{
expressions.Add(content.Substring(start, end - start));
start = end + 1;
}
}
expressions.Add(content.Substring(start, end - start));
var pairs = expressions.Select(s => s.Split(':').Select(sub => sub.Trim()).ToArray());
foreach (var pair in pairs) { props[pair[0].ToLower()] = pair[1]; }
return props;
}
public static string ColorToString(Color c) => $"{c.R},{c.G},{c.B},{c.A}";
public static string Vector2ToString(Vector2 v) => $"[{v.X},{v.Y}]";
public static string NullVector2ToString(Vector2? v) => v.HasValue ? $"[{v.Value.X},{v.Value.Y}]" : "null";
public static string NullIntToString(int? i) => i.HasValue ? $"{i}" : "null";
public static string RectangleToString(Rectangle r) => $"[{r.X},{r.Y},{r.Width},{r.Height}]";
public static string GUIFontToString(GUIFont f) => f.Identifier.Value;
public static string SpriteEffectsToString(SpriteEffects e)
{
if ((int)e == 3) return "FlipBothSides";
else return e.ToString();
}
public static string IEnumerableStringToString(IEnumerable<string> e) => $"[{string.Join(',', e.ToArray())}]";
public static IEnumerable<string> ParseIEnumerableString(string raw)
{
if (raw == null || raw == "") return new List<string>();
string content = raw.Split('[', ']')[1];
return content.Split(',');
}
public static string ParseString(string s) => s; // BaroDev (wide)
//public static GUISoundType ParseGUISoundType(string s) => Enum.Parse<GUISoundType>(s);
public static GUIFont ParseGUIFont(string raw)
{
GUIFont font = GUIStyle.Fonts.GetValueOrDefault(new Identifier(raw.Trim()));
font ??= GUIStyle.Font;
return font;
}
public static SpriteEffects ParseSpriteEffects(string raw)
{
if (raw == "FlipBothSides") return SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically;
else return Enum.Parse<SpriteEffects>(raw);
}
public static int? ParseNullInt(string raw)
{
if (raw == "null") return null;
return int.Parse(raw);
}
public static Vector2? ParseNullVector2(string raw)
{
if (raw == "null") return null;
return ParseVector2(raw);
}
public static Vector2 ParseVector2(string raw)
{
if (raw == null || raw == "") return new Vector2(0, 0);
string content = raw.Split('[', ']')[1];
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
float x = 0;
float y = 0;
float.TryParse(coords.ElementAtOrDefault(0), out x);
float.TryParse(coords.ElementAtOrDefault(1), out y);
return new Vector2(x, y);
}
public static Rectangle ParseRectangle(string raw)
{
if (raw == null || raw == "") return new Rectangle(0, 0, 1, 1);
string content = raw.Split('[', ']')[1];
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
int x = 0;
int y = 0;
int w = 0;
int h = 0;
int.TryParse(coords.ElementAtOrDefault(0), out x);
int.TryParse(coords.ElementAtOrDefault(1), out y);
int.TryParse(coords.ElementAtOrDefault(2), out w);
int.TryParse(coords.ElementAtOrDefault(3), out h);
return new Rectangle(x, y, w, h);
}
public static Color ParseColor(string s) => XMLExtensions.ParseColor(s, false);
public static Dictionary<Type, MethodInfo> Parse;
public static Dictionary<Type, MethodInfo> CustomToString;
internal static void InitStatic()
{
CUI.OnInit += () =>
{
Stopwatch sw = Stopwatch.StartNew();
Parse = new Dictionary<Type, MethodInfo>();
CustomToString = new Dictionary<Type, MethodInfo>();
Parse[typeof(string)] = typeof(CUIExtensions).GetMethod("ParseString");
//Parse[typeof(GUISoundType)] = typeof(CUIExtensions).GetMethod("ParseGUISoundType");
Parse[typeof(Rectangle)] = typeof(CUIExtensions).GetMethod("ParseRectangle");
Parse[typeof(GUIFont)] = typeof(CUIExtensions).GetMethod("ParseGUIFont");
Parse[typeof(Vector2?)] = typeof(CUIExtensions).GetMethod("ParseNullVector2");
Parse[typeof(Vector2)] = typeof(CUIExtensions).GetMethod("ParseVector2");
Parse[typeof(SpriteEffects)] = typeof(CUIExtensions).GetMethod("ParseSpriteEffects");
Parse[typeof(Color)] = typeof(CUIExtensions).GetMethod("ParseColor");
Parse[typeof(int?)] = typeof(CUIExtensions).GetMethod("ParseNullInt");
Parse[typeof(IEnumerable<string>)] = typeof(CUIExtensions).GetMethod("ParseIEnumerableString");
CustomToString[typeof(IEnumerable<string>)] = typeof(CUIExtensions).GetMethod("IEnumerableStringToString");
CustomToString[typeof(int?)] = typeof(CUIExtensions).GetMethod("NullIntToString");
CustomToString[typeof(Color)] = typeof(CUIExtensions).GetMethod("ColorToString");
CustomToString[typeof(SpriteEffects)] = typeof(CUIExtensions).GetMethod("SpriteEffectsToString");
CustomToString[typeof(Vector2)] = typeof(CUIExtensions).GetMethod("Vector2ToString");
CustomToString[typeof(Vector2?)] = typeof(CUIExtensions).GetMethod("NullVector2ToString");
CustomToString[typeof(GUIFont)] = typeof(CUIExtensions).GetMethod("GUIFontToString");
CustomToString[typeof(Rectangle)] = typeof(CUIExtensions).GetMethod("RectangleToString");
CUIDebug.Log($"CUIExtensions.Initialize took {sw.ElapsedMilliseconds}ms");
};
CUI.OnDispose += () =>
{
Parse.Clear();
CustomToString.Clear();
};
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -0,0 +1,391 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using HarmonyLib;
using System.IO;
using System.Xml;
using System.Xml.Linq;
namespace QICrabUI
{
public enum PaletteOrder
{
Primary, Secondary, Tertiary, Quaternary
}
public record PaletteExtractResult(bool Ok, string Value = null);
/// <summary>
/// Contains abstract values that could be referenced in Styles
/// </summary>
public class CUIPalette
{
internal static void InitStatic()
{
CUI.OnInit += () =>
{
Initialize();
};
CUI.OnDispose += () =>
{
LoadedPalettes.Clear();
};
}
public override string ToString() => $"CUIPalette {Name}";
public static string PaletteSetsPath => Path.Combine(CUI.PalettesPath, "Sets");
public static string DefaultPalette = "Blue";
//TODO why is it here? how could sane person find these?
public static bool NotifyExcessivePropStyles { get; set; } = false;
public static bool NotifiMissingPropStyles { get; set; } = true;
public static PaletteExtractResult Extract(string nestedName, PaletteOrder order)
{
CUIPalette palette = order switch
{
PaletteOrder.Primary => Primary,
PaletteOrder.Secondary => Secondary,
PaletteOrder.Tertiary => Tertiary,
PaletteOrder.Quaternary => Quaternary,
_ => Empty,
};
if (!palette.Values.ContainsKey(nestedName)) return new PaletteExtractResult(false);
return new PaletteExtractResult(true, palette.Values[nestedName]);
}
public static CUIPalette Empty => new CUIPalette();
public static Dictionary<string, CUIPalette> LoadedPalettes = new();
public static string Default = "Blue";
private static CUIPalette primary = new CUIPalette();
public static CUIPalette Primary
{
get => primary;
set
{
if (value == null) return;
primary = value;
CUIGlobalStyleResolver.OnPaletteChange(primary);
}
}
private static CUIPalette secondary = new CUIPalette();
public static CUIPalette Secondary
{
get => secondary;
set
{
if (value == null) return;
secondary = value;
CUIGlobalStyleResolver.OnPaletteChange(secondary);
}
}
private static CUIPalette tertiary = new CUIPalette();
public static CUIPalette Tertiary
{
get => tertiary;
set
{
if (value == null) return;
tertiary = value;
CUIGlobalStyleResolver.OnPaletteChange(tertiary);
}
}
private static CUIPalette quaternary = new CUIPalette();
public static CUIPalette Quaternary
{
get => quaternary;
set
{
if (value == null) return;
quaternary = value;
CUIGlobalStyleResolver.OnPaletteChange(quaternary);
}
}
public Dictionary<string, string> Values = new();
public string Name = "???";
public string BaseColor { get; set; } = "";
public static void Initialize()
{
Stopwatch sw = Stopwatch.StartNew();
if (CUI.PalettesPath == null) return;
LoadedPalettes.Clear();
LoadPalettes();
LoadSet(Path.Combine(PaletteSetsPath, DefaultPalette + ".xml"));
// Primary = LoadedPalettes.GetValueOrDefault("red");
// Secondary = LoadedPalettes.GetValueOrDefault("purple");
// Tertiary = LoadedPalettes.GetValueOrDefault("blue");
// Quaternary = LoadedPalettes.GetValueOrDefault("cyan");
CUIDebug.Log($"CUIPalette.Initialize took {sw.ElapsedMilliseconds}ms");
}
public static void LoadPalettes()
{
foreach (string file in Directory.GetFiles(CUI.PalettesPath, "*.xml"))
{
CUIPalette palette = LoadFrom(file);
LoadedPalettes[palette.Name] = palette;
}
}
public static CUIPalette FromXML(XElement root)
{
CUIPalette palette = new CUIPalette();
palette.Name = root.Attribute("Name")?.Value.ToString();
foreach (XElement element in root.Elements())
{
foreach (XAttribute attribute in element.Attributes())
{
palette.Values[$"{element.Name}.{attribute.Name}"] = attribute.Value;
}
if (element.Value != "")
{
palette.Values[$"{element.Name}"] = element.Value;
}
}
return palette;
}
public static CUIPalette LoadFrom(string path)
{
CUIPalette palette = new CUIPalette();
try
{
XDocument xdoc = XDocument.Load(path);
XElement root = xdoc.Root;
palette = CUIPalette.FromXML(root);
palette.Name ??= Path.GetFileNameWithoutExtension(path);
}
catch (Exception e)
{
CUI.Warning($"Failed to load palette from {path}");
CUI.Warning(e);
}
return palette;
}
public XElement ToXML()
{
XElement root = new XElement("Palette");
root.Add(new XAttribute("Name", Name));
root.Add(new XAttribute("BaseColor", BaseColor));
foreach (string key in Values.Keys)
{
string component = key.Split('.').ElementAtOrDefault(0);
string prop = key.Split('.').ElementAtOrDefault(1);
if (component == null) continue;
if (root.Element(component) == null) root.Add(new XElement(component));
if (prop != null)
{
root.Element(component).Add(new XAttribute(prop, Values[key]));
}
else
{
root.Element(component).Value = Values[key];
}
}
return root;
}
public void SaveTo(string path)
{
try
{
XDocument xdoc = new XDocument();
xdoc.Add(this.ToXML());
xdoc.Save(path);
}
catch (Exception e)
{
CUI.Warning($"Failed to save palette to {path}");
CUI.Warning(e);
}
}
public static void PaletteDemo()
{
if (CUI.AssetsPath == null)
{
CUI.Warning($"Can't load PaletteDemo, CUI.AssetsPath is null");
return;
}
void loadFrame(Vector2 offset, PaletteOrder order)
{
CUIFrame frame = CUIComponent.LoadFromFile<CUIFrame>(Path.Combine(CUI.AssetsPath, $"PaletteDemo.xml"));
frame.DeepPalette = order;
frame.Absolute = frame.Absolute with { Position = offset };
frame.AddCommand("Close", (o) => frame.RemoveSelf());
CUI.TopMain.Append(frame);
}
loadFrame(new Vector2(0, 0), PaletteOrder.Primary);
loadFrame(new Vector2(180, 0), PaletteOrder.Secondary);
loadFrame(new Vector2(360, 0), PaletteOrder.Tertiary);
loadFrame(new Vector2(540, 0), PaletteOrder.Quaternary);
}
public static CUIPalette CreatePaletteFromColors(string name, Color colorA, Color? colorb = null)
{
CUIPalette palette = new CUIPalette()
{
Name = name,
BaseColor = CUIExtensions.ColorToString(colorA),
};
Color colorB = colorb ?? Color.Black;
Dictionary<string, Color> colors = new();
colors["Frame.Background"] = colorA.To(colorB, 1.0f);
colors["Header.Background"] = colorA.To(colorB, 0.7f);
colors["Nav.Background"] = colorA.To(colorB, 0.8f);
colors["Main.Background"] = colorA.To(colorB, 0.9f);
colors["Frame.Border"] = colorA.To(colorB, 0.5f);
colors["Header.Border"] = colorA.To(colorB, 0.6f);
colors["Nav.Border"] = colorA.To(colorB, 0.7f);
colors["Main.Border"] = colorA.To(colorB, 0.8f);
colors["Frame.Text"] = colorA.To(Color.White, 0.9f);
colors["Header.Text"] = colorA.To(Color.White, 0.9f);
colors["Nav.Text"] = colorA.To(Color.White, 0.8f);
colors["Main.Text"] = colorA.To(Color.White, 0.8f);
colors["Component.Background"] = Color.Transparent;
colors["Component.Border"] = Color.Transparent;
colors["Component.Text"] = colors["Main.Text"];
colors["Button.Background"] = colorA.To(colorB, 0.0f);
colors["Button.Border"] = colorA.To(colorB, 0.5f);
colors["Button.Disabled"] = colorA.To(new Color(16, 16, 16), 0.8f);
colors["CloseButton.Background"] = colorA.To(Color.White, 0.2f);
colors["DDOption.Background"] = colors["Header.Background"];
colors["DDOption.Border"] = colors["Main.Border"];
colors["DDOption.Hover"] = colorA.To(colorB, 0.5f);
colors["DDOption.Text"] = colors["Main.Text"];
colors["Handle.Background"] = colorA.To(colorB, 0.5f).To(Color.White, 0.2f);
colors["Handle.Grabbed"] = colorA.To(colorB, 0.0f).To(Color.White, 0.2f);
colors["Slider"] = colorA.To(Color.White, 0.7f);
colors["Input.Background"] = colors["Nav.Background"];
colors["Input.Border"] = colors["Nav.Border"];
colors["Input.Text"] = colors["Main.Text"];
colors["Input.Focused"] = colorA;
colors["Input.Invalid"] = Color.Red;
colors["Input.Valid"] = Color.Lime;
colors["Input.Selection"] = colorA.To(Color.White, 0.7f) * 0.5f;
colors["Input.Caret"] = colorA.To(Color.White, 0.7f) * 0.5f;
foreach (var (key, cl) in colors)
{
palette.Values[key] = CUIExtensions.ColorToString(cl);
}
palette.SaveTo(Path.Combine(CUI.PalettesPath, $"{name}.xml"));
LoadedPalettes[name] = palette;
CUI.Log($"Created {name} palette and saved it to {Path.Combine(CUI.PalettesPath, $"{name}.xml")}");
return palette;
}
/// <summary>
/// Packs 4 palettes into 1 set
/// </summary>
/// <param name="setName"></param>
/// <param name="primary"></param>
/// <param name="secondary"></param>
/// <param name="tertiary"></param>
/// <param name="quaternary"></param>
public static void SaveSet(string setName, string primary = "", string secondary = "", string tertiary = "", string quaternary = "")
{
if (setName == null || setName == "") return;
string savePath = Path.Combine(PaletteSetsPath, $"{setName}.xml");
try
{
XDocument xdoc = new XDocument(new XElement("PaletteSet"));
XElement root = xdoc.Root;
root.Add(new XAttribute("Name", setName));
root.Add((LoadedPalettes.GetValueOrDefault(primary ?? "") ?? Primary).ToXML());
root.Add((LoadedPalettes.GetValueOrDefault(secondary ?? "") ?? Secondary).ToXML());
root.Add((LoadedPalettes.GetValueOrDefault(tertiary ?? "") ?? Tertiary).ToXML());
root.Add((LoadedPalettes.GetValueOrDefault(quaternary ?? "") ?? Quaternary).ToXML());
xdoc.Save(savePath);
CUI.Log($"Created {setName} palette set and saved it to {savePath}");
LoadSet(savePath);
}
catch (Exception e)
{
CUI.Warning($"Failed to save palette set to {savePath}");
CUI.Warning(e);
}
}
public static void LoadSet(string path)
{
if (path == null || path == "") return;
try
{
XDocument xdoc = XDocument.Load(path);
XElement root = xdoc.Root;
List<CUIPalette> palettes = new();
foreach (XElement element in root.Elements("Palette"))
{
palettes.Add(CUIPalette.FromXML(element));
}
Primary = palettes.ElementAtOrDefault(0) ?? Empty;
Secondary = palettes.ElementAtOrDefault(1) ?? Empty;
Tertiary = palettes.ElementAtOrDefault(2) ?? Empty;
Quaternary = palettes.ElementAtOrDefault(3) ?? Empty;
}
catch (Exception e)
{
CUI.Warning($"Failed to load palette set from {path}");
CUI.Warning(e);
}
}
}
}

View File

@@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using HarmonyLib;
namespace QICrabUI
{
public static class CUIStylePrefab
{
public static CUIStyle FrameCaption => new CUIStyle()
{
{"BackgroundColor", "CUIPalette.Frame.Border"},
{"Border", "CUIPalette.Frame.Border"},
{"TextColor", "CUIPalette.Frame.Text"},
};
public static CUIStyle Header => new CUIStyle()
{
{"BackgroundColor", "CUIPalette.Header.Background"},
{"Border", "CUIPalette.Header.Border"},
{"TextColor", "CUIPalette.Header.Text"},
};
public static CUIStyle Nav => new CUIStyle()
{
{"BackgroundColor", "CUIPalette.Nav.Background"},
{"Border", "CUIPalette.Nav.Border"},
{"TextColor", "CUIPalette.Nav.Text"},
};
public static CUIStyle Main => new CUIStyle()
{
{"BackgroundColor", "CUIPalette.Main.Background"},
{"Border", "CUIPalette.Main.Border"},
{"TextColor", "CUIPalette.Main.Text"},
};
}
//TODO all this stuff is too specific, there should be more flexible way
public static class CUIPrefab
{
public static CUIFrame ListFrame()
{
CUIFrame frame = new CUIFrame();
frame["list"] = new CUIVerticalList() { Relative = new CUINullRect(0, 0, 1, 1), };
return frame;
}
public static CUIComponent WrapInGroup(string name, CUIComponent content)
{
CUIVerticalList group = new CUIVerticalList() { FitContent = new CUIBool2(false, true), };
group["header"] = new CUITextBlock(name)
{
TextScale = 1.0f,
TextAlign = CUIAnchor.Center,
};
group["content"] = content;
return group;
}
public static CUIComponent Group(string name)
{
CUIVerticalList group = new CUIVerticalList()
{
FitContent = new CUIBool2(false, true),
};
group["header"] = new CUITextBlock(name)
{
TextScale = 1.0f,
TextAlign = CUIAnchor.Center,
};
group["content"] = new CUIVerticalList()
{
FitContent = new CUIBool2(false, true),
};
return group;
}
public static CUIComponent TextAndSliderWithLabel(string name, string command, FloatRange? range = null)
{
CUIComponent wrapper = new CUIVerticalList()
{
FitContent = new CUIBool2(false, true),
Style = CUIStylePrefab.Main,
};
wrapper["label"] = new CUITextBlock(name);
wrapper["controls"] = TextAndSlider(command, range);
return wrapper;
}
public static CUIComponent TextAndSlider(string command, FloatRange? range = null)
{
CUIHorizontalList controls = new CUIHorizontalList()
{
FitContent = new CUIBool2(false, true),
RetranslateCommands = true,
ReflectCommands = true,
};
controls["text"] = new CUITextInput()
{
Absolute = new CUINullRect(w: 20.0f),
Consumes = command,
Command = command,
};
controls["slider"] = new CUISlider()
{
Relative = new CUINullRect(h: 1.0f),
FillEmptySpace = new CUIBool2(true, false),
Consumes = command,
Command = command,
Range = range ?? new FloatRange(0, 1),
};
return controls;
}
public static CUIFrame ListFrameWithHeader()
{
CUIFrame frame = new CUIFrame() { };
frame["layout"] = new CUIVerticalList() { Relative = new CUINullRect(0, 0, 1, 1), };
frame["layout"]["handle"] = new CUIHorizontalList()
{
FitContent = new CUIBool2(false, true),
Direction = CUIDirection.Reverse,
Style = CUIStylePrefab.FrameCaption,
};
frame["layout"]["handle"]["close"] = new CUICloseButton()
{
Absolute = new CUINullRect(0, 0, 15, 15),
Command = "close frame",
};
frame["layout"]["handle"]["caption"] = new CUITextBlock("Caption") { FillEmptySpace = new CUIBool2(true, false) };
frame["layout"]["header"] = new CUIHorizontalList()
{
FitContent = new CUIBool2(false, true),
Style = CUIStylePrefab.Header,
};
frame["layout"]["content"] = new CUIVerticalList()
{
FillEmptySpace = new CUIBool2(false, true),
Style = CUIStylePrefab.Main,
Scrollable = true,
ConsumeDragAndDrop = true,
ConsumeMouseClicks = true,
};
frame["header"] = frame["layout"]["header"];
frame["content"] = frame["layout"]["content"];
frame["caption"] = frame["layout"]["handle"]["caption"];
return frame;
}
public static CUIHorizontalList TickboxWithLabel(string text, string command, float tickboxSize = 22.0f)
{
CUIHorizontalList list = new CUIHorizontalList()
{
FitContent = new CUIBool2(true, true),
};
list["tickbox"] = new CUITickBox()
{
Absolute = new CUINullRect(w: tickboxSize, h: tickboxSize),
Command = command,
Consumes = command,
};
list["text"] = new CUITextBlock()
{
Text = text,
TextAlign = CUIAnchor.CenterLeft,
};
return list;
}
//TODO this is now too specific and shouldn't be here
public static CUIHorizontalList InputWithValidation(PropertyInfo pi, string command)
{
string ToUserFriendly(Type T)
{
if (T == typeof(bool)) return "Boolean";
if (T == typeof(int)) return "Integer";
if (T == typeof(float)) return "Float";
return T.Name;
}
CUIHorizontalList list = new CUIHorizontalList()
{
FitContent = new CUIBool2(true, true),
Border = new CUIBorder(),
};
list["input"] = new CUITextInput()
{
AbsoluteMin = new CUINullRect(w: 100),
Relative = new CUINullRect(w: 0.3f),
Command = command,
Consumes = command,
VatidationType = pi.PropertyType,
};
list["label"] = new CUITextBlock()
{
FillEmptySpace = new CUIBool2(true, false),
Text = $"{ToUserFriendly(pi.PropertyType)} {pi.Name}",
TextAlign = CUIAnchor.CenterLeft,
BackgroundSprite = new CUISprite("gradient.png"),
Style = new CUIStyle(){
{"BackgroundColor", "CUIPalette.Text3.Background"},
{"Border", "CUIPalette.Text3.Border"},
{"TextColor", "CUIPalette.Text3.Text"},
},
};
return list;
}
}
}

View File

@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.IO;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using HarmonyLib;
using System.Xml;
using System.Xml.Linq;
namespace QICrabUI
{
public class TypeTreeNode
{
public Type T;
public TypeTreeNode Parent;
public List<TypeTreeNode> Children = new();
public CUITypeMetaData Meta => CUITypeMetaData.Get(T);
public void Add(TypeTreeNode child)
{
child.Parent = this;
Children.Add(child);
}
public TypeTreeNode(Type t) => T = t;
public override string ToString() => T?.ToString() ?? "null";
public void RunRecursive(Action<TypeTreeNode> action)
{
action(this);
foreach (TypeTreeNode child in Children)
{
child.RunRecursive(action);
}
}
}
[CUIInternal]
public class CUIReflection
{
internal static void InitStatic()
{
CUI.OnInit += () =>
{
Stopwatch sw = Stopwatch.StartNew();
FindCUITypes();
FormCUITypeTree();
CUIDebug.Log($"CUIReflection.Initialize took {sw.ElapsedMilliseconds}ms");
};
CUI.OnDispose += () =>
{
CUITypes.Clear();
CUILayoutTypes.Clear();
CUITypeTree.Clear();
};
}
public record TypePair(Type type, Type baseType);
public static Dictionary<Type, TypeTreeNode> CUITypeTree = new();
public static Dictionary<string, Type> CUILayoutTypes = new();
public static Dictionary<string, Type> CUITypes = new Dictionary<string, Type>();
public static void FormCUITypeTree()
{
List<TypePair> Pustoe = CUITypes.Values.Select(t => new TypePair(t, t.BaseType)).ToList();
List<TypePair> Porojnee = new List<TypePair>();
while (Pustoe.Count > 0)
{
Porojnee = new List<TypePair>();
foreach (TypePair pair in Pustoe)
{
// Tree root CUIComponent
if (pair.baseType == typeof(object))
{
CUITypeTree[pair.type] = new TypeTreeNode(pair.type);
continue;
}
// Derived class
if (CUITypeTree.ContainsKey(pair.baseType))
{
CUITypeTree[pair.type] = new TypeTreeNode(pair.type);
CUITypeTree[pair.baseType].Add(CUITypeTree[pair.type]);
continue;
}
// Base class not in tree yet
Porojnee.Add(pair);
}
Pustoe.Clear();
Pustoe = Porojnee;
}
//foreach (TypeTreeNode node in CUITypeTree.Values) CUI.Log(node);
}
public static void FindCUITypes()
{
Assembly CUIAssembly = Assembly.GetAssembly(typeof(CUI));
Assembly CallingAssembly = Assembly.GetCallingAssembly();
CUITypes["CUIComponent"] = typeof(CUIComponent);
CUILayoutTypes["CUILayout"] = typeof(CUILayout);
//TODO Nested types might have same name, think how to separate them
// lua and style resolver uses them
// string name = t.Name;
// if (t.DeclaringType != null) name = $"{t.DeclaringType.Name}.{t.Name}";
// CUITypes[name] = t;
foreach (Type t in CallingAssembly.GetTypes())
{
if (t.IsSubclassOf(typeof(CUIComponent))) CUITypes[t.Name] = t;
if (t.IsSubclassOf(typeof(CUILayout))) CUILayoutTypes[t.Name] = t;
}
foreach (Type t in CUIAssembly.GetTypes())
{
if (t.IsSubclassOf(typeof(CUIComponent))) CUITypes[t.Name] = t;
if (t.IsSubclassOf(typeof(CUILayout))) CUILayoutTypes[t.Name] = t;
}
}
public static Type GetComponentTypeByName(string name)
{
return CUITypes.GetValueOrDefault(name);
}
public static object GetDefault(object obj)
{
FieldInfo defField = obj.GetType().GetField("Default", BindingFlags.Static | BindingFlags.Public);
if (defField == null) return null;
return defField.GetValue(null);
}
public static object GetNestedValue(object obj, string nestedName)
{
string[] names = nestedName.Split('.');
foreach (string name in names)
{
FieldInfo fi = obj.GetType().GetField(name, AccessTools.all);
PropertyInfo pi = obj.GetType().GetProperty(name, AccessTools.all);
if (fi != null)
{
obj = fi.GetValue(obj);
continue;
}
if (pi != null)
{
obj = pi.GetValue(obj);
continue;
}
return null;
}
return obj;
}
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
namespace QICrabUI
{
/// <summary>
/// Class for loading textures without duplicates
/// Also disposes all loaded textures automatically
/// </summary>
public class CUITextureManager : IDisposable
{
// private static Texture2D backupTexture;
// public static Texture2D BackupTexture
// {
// get
// {
// if (backupTexture == null)
// {
// backupTexture = new Texture2D(GameMain.Instance.GraphicsDevice, 1, 1);
// backupTexture.SetData(new Color[] { Color.White });
// }
// return backupTexture;
// }
// }
/// <summary>
/// Path to additional PNGs, it can be set in CUI
/// </summary>
public string PGNAssets { get; set; }
public static Texture2D BackupTexture => GUI.WhiteTexture;
public Dictionary<string, Texture2D> LoadedTextures = new();
public void DisposeAllTextures()
{
foreach (Texture2D texture in LoadedTextures.Values)
{
if (texture == BackupTexture) continue;
texture.Dispose();
}
}
public string NormalizePath(string path)
{
return path; //TODO
}
public CUISprite GetSprite(string path, Rectangle? sourceRect = null, CUISpriteDrawMode? drawMode = null, SpriteEffects? effects = null)
{
CUISprite sprite = new CUISprite(GetTexture(path))
{
Path = path,
};
if (sourceRect.HasValue) sprite.SourceRect = sourceRect.Value;
if (drawMode.HasValue) sprite.DrawMode = drawMode.Value;
if (effects.HasValue) sprite.Effects = effects.Value;
return sprite;
}
/// <summary>
/// 32x32 square on (x,y) position from CUI.png
/// </summary>
public CUISprite GetCUISprite(int x, int y, CUISpriteDrawMode? drawMode = null, SpriteEffects? effects = null)
{
return GetSprite(CUI.CUITexturePath, new Rectangle(x * 32, y * 32, 32, 32), drawMode, effects);
}
private Texture2D TryLoad(string path)
{
Texture2D texture = null;
try
{
if (File.Exists(path))
{
using (FileStream fs = File.OpenRead(path))
{
texture = Texture2D.FromStream(GameMain.Instance.GraphicsDevice, fs);
}
}
}
catch { }
return texture;
}
public Texture2D GetTexture(string path)
{
if (LoadedTextures == null)
{
CUI.Error($"LoadedTextures was null");
return BackupTexture;
}
if (LoadedTextures.ContainsKey(path)) return LoadedTextures[path];
Texture2D loaded = null;
if (CUI.AssetsPath != null) loaded ??= TryLoad(Path.Combine(CUI.AssetsPath, path));
if (PGNAssets != null) loaded ??= TryLoad(Path.Combine(PGNAssets, path));
loaded ??= TryLoad(path);
if (loaded == null)
{
CUI.Warning($"Coudn't find {path} texture, setting it to backup texture");
loaded ??= BackupTexture;
if (PGNAssets == null)
{
CUI.Warning($"Note: It was loaded before CUI.PGNAssets was set");
}
}
LoadedTextures[path] = loaded;
return loaded;
}
public void Dispose()
{
DisposeAllTextures();
LoadedTextures.Clear();
LoadedTextures = null;
}
}
}

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Runtime.CompilerServices;
using HarmonyLib;
namespace QICrabUI
{
public class CUISerializableAttribute : System.Attribute { }
public class DontSerializeAttribute : System.Attribute { }
public class CalculatedAttribute : System.Attribute { }
public class NoDefaultAttribute : System.Attribute { }
public enum CUIAttribute
{
CUISerializable,
DontSerialize,
Calculated,
}
public class CUITypeMetaData
{
internal static void InitStatic()
{
CUI.OnInit += () =>
{
TypeMetaData = new Dictionary<Type, CUITypeMetaData>();
};
CUI.OnDispose += () =>
{
TypeMetaData.Clear();
};
}
public static Dictionary<Type, CUITypeMetaData> TypeMetaData;
public static CUITypeMetaData Get(Type type)
{
if (!TypeMetaData.ContainsKey(type)) new CUITypeMetaData(type);
return TypeMetaData[type];
}
public Type CUIType;
public CUIComponent Default;
public CUIStyle defaultStyle; public CUIStyle DefaultStyle
{
get => defaultStyle;
set
{
if (defaultStyle == value) return;
if (defaultStyle != null)
{
defaultStyle.OnUse -= HandleStyleChange;
defaultStyle.OnPropChanged -= HandleStylePropChange;
}
defaultStyle = value;
if (defaultStyle != null)
{
defaultStyle.OnUse += HandleStyleChange;
defaultStyle.OnPropChanged += HandleStylePropChange;
}
HandleStyleChange(defaultStyle);
}
}
private void HandleStylePropChange(string key, string value)
{
CUIGlobalStyleResolver.OnDefaultStylePropChanged(CUIType, key);
}
private void HandleStyleChange(CUIStyle s)
{
CUIGlobalStyleResolver.OnDefaultStyleChanged(CUIType);
}
public CUIStyle ResolvedDefaultStyle { get; set; }
public SortedDictionary<string, PropertyInfo> Serializable = new();
public SortedDictionary<string, PropertyInfo> Calculated = new();
public SortedDictionary<string, PropertyInfo> Assignable = new();
public CUITypeMetaData(Type type)
{
TypeMetaData[type] = this; // !!!
CUIType = type;
foreach (PropertyInfo pi in type.GetProperties(AccessTools.all))
{
if (Attribute.IsDefined(pi, typeof(CUISerializableAttribute)))
{
Serializable[pi.Name] = pi;
}
if (Attribute.IsDefined(pi, typeof(CalculatedAttribute)))
{
Calculated[pi.Name] = pi;
}
if (pi.CanWrite)
{
Assignable[pi.Name] = pi;
}
}
try
{
DefaultStyle = new CUIStyle();
if (!Attribute.IsDefined(type, typeof(NoDefaultAttribute)))
{
Default = (CUIComponent)Activator.CreateInstance(type);
}
}
catch (Exception e)
{
if (e is System.MissingMethodException) return;
CUI.Warning($"In CUITypeMetaData {type}\n{e}\n");
}
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma;
using HarmonyLib;
using Microsoft.Xna.Framework;
namespace QICrabUI
{
public class ModStorage
{
private static Dictionary<string, object> GetOrCreateRepo()
{
if (GUI.Canvas.GUIComponent is not GUIButton)
{
GUI.Canvas.GUIComponent = new GUIButton(new RectTransform(new Point(0, 0)));
}
if (GUI.Canvas.GUIComponent.UserData is not Dictionary<string, object>)
{
GUI.Canvas.GUIComponent.UserData = new Dictionary<string, object>();
}
return (Dictionary<string, object>)GUI.Canvas.GUIComponent.UserData;
}
public static object Get<TValue>(string key) => (TValue)Get(key);
public static object Get(string key)
{
Dictionary<string, object> repo = GetOrCreateRepo();
return repo.GetValueOrDefault(key);
}
public static void Set(string key, object value)
{
Dictionary<string, object> repo = GetOrCreateRepo();
repo[key] = value;
}
public static bool Has(string key)
{
Dictionary<string, object> repo = GetOrCreateRepo();
return repo.ContainsKey(key);
}
}
}

View File

@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// Base class for all layouts
/// </summary>
public class CUILayout
{
public CUIComponent Host;
//NOTE: This looks ugly, but no matter how i try to isolate this logic it gets only uglier
// i've been stuck here for too long so i'll just do this
// and each update pattern in fact used only once, so i think no big deal
/// <summary>
/// This is just for debug, don't use it
/// </summary>
public void ForceMarkUnchanged()
{
decorChanged = false;
changed = false;
absoluteChanged = false;
foreach (CUIComponent child in Host.Children)
{
child.Layout.ForceMarkUnchanged();
}
}
private void propagateChangedDown()
{
changed = true;
DecorChanged = true;
foreach (CUIComponent child in Host.Children)
{
child.Layout.propagateChangedDown();
}
}
protected bool changed = true; public bool Changed
{
get => changed;
set
{
changed = value;
if (value)
{
if (Host.Parent != null) Host.Parent.Layout.propagateChangedDown();
else propagateChangedDown();
}
}
}
private void propagateDecorChangedDown()
{
DecorChanged = true;
foreach (CUIComponent child in Host.Children)
{
child.Layout.propagateDecorChangedDown();
}
}
/// <summary>
/// It doesn't optimize anything
/// </summary>
public bool SelfAndParentChanged
{
set
{
if (value)
{
changed = true;
DecorChanged = true;
if (Host.Parent != null)
{
Host.Parent.Layout.changed = true;
Host.Parent.Layout.propagateDecorChangedDown();
}
}
}
}
public bool ChildChanged
{
set
{
if (value) propagateChangedDown();
}
}
private void propagateAbsoluteChangedUp()
{
absoluteChanged = true;
Host.Parent?.Layout.propagateAbsoluteChangedUp();
}
protected bool absoluteChanged = true; public bool AbsoluteChanged
{
get => absoluteChanged;
set
{
if (!value) absoluteChanged = false;
if (value && Host.Parent != null) Host.Parent.Layout.absoluteChanged = true;
//if (value && Host.Parent != null) Host.Parent.Layout.propagateAbsoluteChangedUp();
}
}
protected bool decorChanged = true; public bool DecorChanged
{
get => decorChanged;
set
{
decorChanged = value;
}
}
internal virtual void Update()
{
if (Changed)
{
if (Host.CullChildren)
{
foreach (CUIComponent c in Host.Children)
{
c.CulledOut = !c.UnCullable && !c.Real.Intersect(Host.Real);
}
}
Changed = false;
}
}
internal virtual void UpdateDecor()
{
if (DecorChanged)
{
Host.UpdatePseudoChildren();
DecorChanged = false;
}
}
internal virtual void ResizeToContent()
{
if (AbsoluteChanged && (Host.FitContent.X || Host.FitContent.Y))
{
// do something
}
AbsoluteChanged = false;
}
public CUILayout() { }
public CUILayout(CUIComponent host)
{
Host = host;
}
public override string ToString() => this.GetType().Name;
public static CUILayout Parse(string raw)
{
if (raw != null)
{
raw = raw.Trim();
if (CUIReflection.CUILayoutTypes.ContainsKey(raw))
{
return (CUILayout)Activator.CreateInstance(CUIReflection.CUILayoutTypes[raw]);
}
}
return new CUILayoutSimple();
}
}
}

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// A Grid containing children in its cells
/// Dividing host into rows and columns basing on GridTemplate
/// And then placing children basin on their GridCell
/// </summary>
public class CUILayoutGrid : CUILayout
{
public enum TrackSizeType
{
Unknown,
Absolute,
Relative,
Fractional,
}
public class GridTrack
{
public TrackSizeType Type;
public float? Absolute;
public float? Relative;
public float? Fractional;
public float Start;
public float End;
public float Size;
public float RealSize(float hostSize)
{
float size = 0;
if (Absolute.HasValue) size = Absolute.Value;
if (Relative.HasValue) size = Relative.Value * hostSize;
return size;
}
public GridTrack(string value)
{
value = value.Trim();
float f = 0;
if (value.EndsWith("fr"))
{
if (float.TryParse(value.Substring(0, value.Length - 2), out f))
{
Fractional = f;
Type = TrackSizeType.Fractional;
}
}
if (value.EndsWith("%"))
{
if (float.TryParse(value.Substring(0, value.Length - 1), out f))
{
Relative = f / 100f;
Type = TrackSizeType.Relative;
}
}
if (float.TryParse(value, out f))
{
Absolute = f;
Type = TrackSizeType.Absolute;
}
}
public override string ToString() => $"[{Absolute},{Relative},{Fractional}]";
}
List<GridTrack> Rows = new();
List<GridTrack> Columns = new();
public void CalculateTracks()
{
Rows.Clear();
Columns.Clear();
if (Host.GridTemplateRows != null)
{
foreach (string s in Host.GridTemplateRows.Split(' '))
{
Rows.Add(new GridTrack(s));
}
}
if (Host.GridTemplateColumns != null)
{
foreach (string s in Host.GridTemplateColumns.Split(' '))
{
Columns.Add(new GridTrack(s));
}
}
if (Rows.Count == 0) Rows.Add(new GridTrack("100%"));
if (Columns.Count == 0) Columns.Add(new GridTrack("100%"));
float x = 0;
foreach (GridTrack track in Columns)
{
track.Start = x;
track.Size = track.RealSize(Host.Real.Width);
x += track.Size;
track.End = x;
}
float y = 0;
foreach (GridTrack track in Rows)
{
track.Start = y;
track.Size = track.RealSize(Host.Real.Height);
y += track.Size;
track.End = y;
}
}
internal override void Update()
{
if (Changed && Host.Children.Count > 0)
{
Host.InvokeOnLayoutUpdated();
CalculateTracks();
foreach (CUIComponent c in Host.Children)
{
float x = 0;
float y = 0;
float w = 0;
float h = 0;
int startCellX = 0;
int startCellY = 0;
if (c.GridStartCell != null)
{
startCellX = Math.Clamp(c.GridStartCell.Value.X, 0, Rows.Count);
startCellY = Math.Clamp(c.GridStartCell.Value.Y, 0, Columns.Count);
}
int endCellX = 0;
int endCellY = 0;
if (c.GridEndCell != null)
{
endCellX = Math.Clamp(c.GridEndCell.Value.X, 0, Rows.Count);
endCellY = Math.Clamp(c.GridEndCell.Value.Y, 0, Columns.Count);
}
CUIRect real = new CUIRect(
Columns[startCellX].Start,
Rows[startCellY].Start,
Columns[endCellX].End - Columns[startCellX].Start,
Rows[endCellY].End - Rows[startCellY].Start
);
real = real.Shift(Host.Real.Position);
c.AmIOkWithThisSize(real.Size);
c.SetReal(real, "Grid Layout update");
}
}
base.Update();
}
public CUILayoutGrid() : base() { }
public CUILayoutGrid(CUIComponent host) : base(host) { }
}
}

View File

@@ -0,0 +1,257 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// Resizing components to it's Height and placing them sequentially
/// </summary>
public class CUILayoutHorizontalList : CUILayout
{
internal float TotalWidth;
public CUIDirection Direction;
public bool ResizeToHostHeight { get; set; } = true;
private class CUIComponentSize
{
public CUIComponent Component;
public Vector2 Size;
public CUIComponentSize(CUIComponent component, Vector2 size)
{
Component = component;
Size = size;
}
}
private List<CUIComponentSize> Sizes = new List<CUIComponentSize>();
private List<CUIComponentSize> Resizible = new List<CUIComponentSize>();
internal override void Update()
{
if (Changed)
{
Host.InvokeOnLayoutUpdated();
Sizes.Clear();
Resizible.Clear();
TotalWidth = 0;
foreach (CUIComponent c in Host.Children)
{
float h = 0;
float w = 0;
if (ResizeToHostHeight)
{
h = Host.Real.Height;
}
else
{
if (c.Relative.Height.HasValue) h = c.Relative.Height.Value * Host.Real.Height;
if (c.CrossRelative.Height.HasValue) h = c.CrossRelative.Height.Value * Host.Real.Width;
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
if (c.RelativeMin.Height.HasValue) h = Math.Max(h, c.RelativeMin.Height.Value * Host.Real.Height);
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
if (!c.RelativeMin.Height.HasValue && !c.AbsoluteMin.Height.HasValue && c.ForcedMinSize.Y.HasValue)
{
h = Math.Max(h, c.ForcedMinSize.Y.Value);
}
if (c.RelativeMax.Height.HasValue) h = Math.Min(h, c.RelativeMax.Height.Value * Host.Real.Height);
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
}
Vector2 s = new Vector2(w, h);
if (!c.FillEmptySpace.X && !c.Ghost.X)
{
if (c.Relative.Width.HasValue)
{
w = c.Relative.Width.Value * Host.Real.Width;
CUIDebug.Capture(Host, c, "HorizontalList.Update", "Relative.Width", "w", w.ToString());
}
if (c.CrossRelative.Width.HasValue)
{
w = c.CrossRelative.Width.Value * Host.Real.Height;
CUIDebug.Capture(Host, c, "HorizontalList.Update", "CrossRelative.Width", "w", w.ToString());
}
if (c.Absolute.Width.HasValue)
{
w = c.Absolute.Width.Value;
CUIDebug.Capture(Host, c, "HorizontalList.Update", "Absolute.Width", "w", w.ToString());
}
if (c.RelativeMin.Width.HasValue)
{
w = Math.Max(w, c.RelativeMin.Width.Value * Host.Real.Width);
CUIDebug.Capture(Host, c, "HorizontalList.Update", "RelativeMin.Width", "w", w.ToString());
}
if (c.AbsoluteMin.Width.HasValue)
{
w = Math.Max(w, c.AbsoluteMin.Width.Value);
CUIDebug.Capture(Host, c, "HorizontalList.Update", "AbsoluteMin.Width", "w", w.ToString());
}
if (!c.RelativeMin.Width.HasValue && !c.AbsoluteMin.Width.HasValue && c.ForcedMinSize.X.HasValue)
{
w = Math.Max(w, c.ForcedMinSize.X.Value);
CUIDebug.Capture(Host, c, "HorizontalList.Update", "ForcedMinSize.X", "w", w.ToString());
}
if (c.RelativeMax.Width.HasValue)
{
w = Math.Min(w, c.RelativeMax.Width.Value * Host.Real.Width);
CUIDebug.Capture(Host, c, "HorizontalList.Update", "RelativeMax.Width", "w", w.ToString());
}
if (c.AbsoluteMax.Width.HasValue)
{
w = Math.Min(w, c.AbsoluteMax.Width.Value);
CUIDebug.Capture(Host, c, "HorizontalList.Update", "AbsoluteMax.Width", "w", w.ToString());
}
s = new Vector2(w, h);
Vector2 okSize = c.AmIOkWithThisSize(s);
CUIDebug.Capture(Host, c, "HorizontalList.Update", "AmIOkWithThisSize", "s", okSize.ToString());
s = okSize;
if (!c.Fixed) s = new Vector2(s.X / c.Scale, s.Y);
TotalWidth += s.X;
}
CUIComponentSize size = new CUIComponentSize(c, s);
Sizes.Add(size);
if (c.FillEmptySpace.X) Resizible.Add(size);
}
float dif = Math.Max(0, Host.Real.Width - TotalWidth);
Resizible.ForEach(c =>
{
c.Size = c.Component.AmIOkWithThisSize(new Vector2((float)Math.Round(dif / Resizible.Count), c.Size.Y));
//c.Size = new Vector2(dif / Resizible.Count, c.Size.Y);
CUIDebug.Capture(Host, c.Component, "HorizontalList.Update", "Resizible.ForEach", "c.Size", c.Size.ToString());
});
CUI3DOffset offset = Host.ChildOffsetBounds.Check(Host.ChildrenOffset);
if (Direction == CUIDirection.Straight)
{
float x = 0;
foreach (CUIComponentSize c in Sizes)
{
CUIRect real;
if (Host.ChildrenBoundaries != null)
{
real = Host.ChildrenBoundaries(Host.Real).Check(x, 0, c.Size.X, c.Size.Y);
}
else
{
real = new CUIRect(x, 0, c.Size.X, c.Size.Y);
}
real = offset.Transform(real);
real = real.Shift(Host.Real.Position);
c.Component.SetReal(real, "HorizontalList layout update");
x += c.Size.X;
}
}
if (Direction == CUIDirection.Reverse)
{
float x = Host.Real.Width;
foreach (CUIComponentSize c in Sizes)
{
x -= c.Size.X;
CUIRect real;
if (Host.ChildrenBoundaries != null)
{
real = Host.ChildrenBoundaries(Host.Real).Check(x, 0, c.Size.X, c.Size.Y);
}
else
{
real = new CUIRect(x, 0, c.Size.X, c.Size.Y);
}
real = offset.Transform(real);
real = real.Shift(Host.Real.Position);
c.Component.SetReal(real, "HorizontalList layout update");
}
}
}
base.Update();
}
//TODO sync with vlist
internal override void ResizeToContent()
{
if (AbsoluteChanged && Host.FitContent.X)
{
float tw = 0;
foreach (CUIComponent c in Host.Children)
{
if (c.Ghost.X) continue;
float w = 0;
if (!c.FillEmptySpace.X)
{
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
else if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
tw += w;
}
}
CUIDebug.Capture(null, Host, "HorizontalList ResizeToContent", "tw", "ForcedMinSize.X", tw.ToString());
Host.SetForcedMinSize(Host.ForcedMinSize with { X = tw });
}
if (AbsoluteChanged && Host.FitContent.Y)
{
float th = 0;
foreach (CUIComponent c in Host.Children)
{
if (c.Ghost.Y) continue;
float h = 0;
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
else if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
th = Math.Max(th, h);
}
CUIDebug.Capture(null, Host, "HorizontalList ResizeToContent", "th", "ForcedMinSize.Y", th.ToString());
Host.SetForcedMinSize(Host.ForcedMinSize with { Y = th });
}
base.ResizeToContent();
}
public CUILayoutHorizontalList() : base() { }
public CUILayoutHorizontalList(CUIDirection d, CUIComponent host = null) : base(host)
{
Direction = d;
}
}
}

View File

@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// Default layout, simple descartes coordinate plane
/// </summary>
public class CUILayoutSimple : CUILayout
{
internal override void Update()
{
if (Changed && Host.Children.Count > 0)
{
Host.InvokeOnLayoutUpdated();
CUI3DOffset offset = Host.ChildOffsetBounds.Check(Host.ChildrenOffset);
foreach (CUIComponent c in Host.Children)
{
float x, y, w, h;
x = 0;
if (c.Relative.Left.HasValue) x = c.Relative.Left.Value * Host.Real.Width;
if (c.CrossRelative.Left.HasValue) x = c.CrossRelative.Left.Value * Host.Real.Height;
if (c.Absolute.Left.HasValue) x = c.Absolute.Left.Value;
if (c.RelativeMin.Left.HasValue) x = Math.Max(x, c.RelativeMin.Left.Value * Host.Real.Width);
if (c.AbsoluteMin.Left.HasValue) x = Math.Max(x, c.AbsoluteMin.Left.Value);
if (c.RelativeMax.Left.HasValue) x = Math.Min(x, c.RelativeMax.Left.Value * Host.Real.Width);
if (c.AbsoluteMax.Left.HasValue) x = Math.Min(x, c.AbsoluteMax.Left.Value);
y = 0;
if (c.Relative.Top.HasValue) y = c.Relative.Top.Value * Host.Real.Height;
if (c.CrossRelative.Top.HasValue) y = c.CrossRelative.Top.Value * Host.Real.Width;
if (c.Absolute.Top.HasValue) y = c.Absolute.Top.Value;
if (c.RelativeMin.Top.HasValue) y = Math.Max(y, c.RelativeMin.Top.Value * Host.Real.Height);
if (c.AbsoluteMin.Top.HasValue) y = Math.Max(y, c.AbsoluteMin.Top.Value);
if (c.RelativeMax.Top.HasValue) y = Math.Min(y, c.RelativeMax.Top.Value * Host.Real.Height);
if (c.AbsoluteMax.Top.HasValue) y = Math.Min(y, c.AbsoluteMax.Top.Value);
w = 0;
if (c.Relative.Width.HasValue) w = c.Relative.Width.Value * Host.Real.Width;
if (c.CrossRelative.Width.HasValue) w = c.CrossRelative.Width.Value * Host.Real.Height;
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
if (c.RelativeMin.Width.HasValue) w = Math.Max(w, c.RelativeMin.Width.Value * Host.Real.Width);
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
if (c.RelativeMax.Width.HasValue) w = Math.Min(w, c.RelativeMax.Width.Value * Host.Real.Width);
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
h = 0;
if (c.Relative.Height.HasValue) h = c.Relative.Height.Value * Host.Real.Height;
if (c.CrossRelative.Height.HasValue) h = c.CrossRelative.Height.Value * Host.Real.Width;
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
if (c.RelativeMin.Height.HasValue) h = Math.Max(h, c.RelativeMin.Height.Value * Host.Real.Height);
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
if (c.RelativeMax.Height.HasValue) h = Math.Min(h, c.RelativeMax.Height.Value * Host.Real.Height);
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
(w, h) = c.AmIOkWithThisSize(new Vector2(w, h));
(x, y) = CUIAnchor.GetChildPos(
new CUIRect(Vector2.Zero, Host.Real.Size),
c.ParentAnchor ?? c.Anchor,
new Vector2(x, y),
new Vector2(w, h),
c.Anchor
);
CUIRect real;
if (Host.ChildrenBoundaries != null)
{
real = Host.ChildrenBoundaries(Host.Real).Check(x, y, w, h);
}
else
{
real = new CUIRect(x, y, w, h);
}
if (!c.Fixed)
{
real = offset.Transform(real);
}
//TODO guh...
real = real.Shift(Host.Real.Position);
c.SetReal(real, "Simple Layout update");
}
}
base.Update();
}
internal override void ResizeToContent()
{
if (AbsoluteChanged && Host.FitContent.X)
{
float rightmostRight = 0;
foreach (CUIComponent c in Host.Children)
{
if (c.Ghost.X) continue;
float x = 0;
float w = 0;
if (c.Absolute.Left.HasValue) x = c.Absolute.Left.Value;
if (c.AbsoluteMin.Left.HasValue) x = Math.Max(x, c.AbsoluteMin.Left.Value);
if (c.AbsoluteMax.Left.HasValue) x = Math.Min(x, c.AbsoluteMax.Left.Value);
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
else if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
rightmostRight = Math.Max(rightmostRight, x + w);
}
Host.SetForcedMinSize(Host.ForcedMinSize with { X = rightmostRight });
}
if (AbsoluteChanged && Host.FitContent.Y)
{
float bottommostBottom = 0;
foreach (CUIComponent c in Host.Children)
{
if (c.Ghost.Y) continue;
float y = 0;
float h = 0;
if (c.Absolute.Top.HasValue) y = c.Absolute.Top.Value;
if (c.AbsoluteMin.Top.HasValue) y = Math.Max(y, c.AbsoluteMin.Top.Value);
if (c.AbsoluteMax.Top.HasValue) y = Math.Min(y, c.AbsoluteMax.Top.Value);
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
else if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
bottommostBottom = Math.Max(bottommostBottom, y + h);
}
Host.SetForcedMinSize(Host.ForcedMinSize with { Y = bottommostBottom });
}
base.ResizeToContent();
}
public CUILayoutSimple() : base() { }
public CUILayoutSimple(CUIComponent host) : base(host) { }
}
}

View File

@@ -0,0 +1,260 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// Resizing components to it's Width and placing them sequentially
/// </summary>
public class CUILayoutVerticalList : CUILayout
{
internal float TotalHeight;
public CUIDirection Direction;
public float Gap { get; set; }
public bool ResizeToHostWidth { get; set; } = true;
private class CUIComponentSize
{
public CUIComponent Component;
public Vector2 Size;
public CUIComponentSize(CUIComponent component, Vector2 size)
{
Component = component;
Size = size;
}
}
private List<CUIComponentSize> Sizes = new List<CUIComponentSize>();
private List<CUIComponentSize> Resizible = new List<CUIComponentSize>();
internal override void Update()
{
Stopwatch sw = new Stopwatch();
if (Changed)
{
Host.InvokeOnLayoutUpdated();
Sizes.Clear();
Resizible.Clear();
TotalHeight = 0;
sw.Restart();
foreach (CUIComponent c in Host.Children)
{
float h = 0;
float w = 0;
if (ResizeToHostWidth)
{
w = Host.Real.Width;
}
else
{
if (c.Relative.Width.HasValue) w = c.Relative.Width.Value * Host.Real.Width;
if (c.CrossRelative.Width.HasValue) w = c.CrossRelative.Width.Value * Host.Real.Height;
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
if (c.RelativeMin.Width.HasValue) w = Math.Max(w, c.RelativeMin.Width.Value * Host.Real.Width);
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
if (!c.RelativeMin.Width.HasValue && !c.AbsoluteMin.Width.HasValue && c.ForcedMinSize.X.HasValue)
{
w = Math.Max(w, c.ForcedMinSize.X.Value);
}
if (c.RelativeMax.Width.HasValue) w = Math.Min(w, c.RelativeMax.Width.Value * Host.Real.Width);
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
}
Vector2 s = new Vector2(w, h);
if (!c.FillEmptySpace.Y && !c.Ghost.Y)
{
if (c.Relative.Height.HasValue)
{
h = c.Relative.Height.Value * Host.Real.Height;
CUIDebug.Capture(Host, c, "VerticalList.Update", "Relative.Height", "h", h.ToString());
}
if (c.CrossRelative.Height.HasValue)
{
h = c.CrossRelative.Height.Value * Host.Real.Width;
CUIDebug.Capture(Host, c, "VerticalList.Update", "CrossRelative.Height", "h", h.ToString());
}
if (c.Absolute.Height.HasValue)
{
h = c.Absolute.Height.Value;
CUIDebug.Capture(Host, c, "VerticalList.Update", "Absolute.Height", "h", h.ToString());
}
if (c.RelativeMin.Height.HasValue)
{
h = Math.Max(h, c.RelativeMin.Height.Value * Host.Real.Height);
CUIDebug.Capture(Host, c, "VerticalList.Update", "RelativeMin.Height", "h", h.ToString());
}
if (c.AbsoluteMin.Height.HasValue)
{
h = Math.Max(h, c.AbsoluteMin.Height.Value);
CUIDebug.Capture(Host, c, "VerticalList.Update", "AbsoluteMin.Height", "h", h.ToString());
}
if (!c.RelativeMin.Height.HasValue && !c.AbsoluteMin.Height.HasValue && c.ForcedMinSize.Y.HasValue)
{
h = Math.Max(h, c.ForcedMinSize.Y.Value);
CUIDebug.Capture(Host, c, "VerticalList.Update", "ForcedMinSize.Y", "h", h.ToString());
}
if (c.RelativeMax.Height.HasValue)
{
h = Math.Min(h, c.RelativeMax.Height.Value * Host.Real.Height);
CUIDebug.Capture(Host, c, "VerticalList.Update", "RelativeMax.Height", "h", h.ToString());
}
if (c.AbsoluteMax.Height.HasValue)
{
h = Math.Min(h, c.AbsoluteMax.Height.Value);
CUIDebug.Capture(Host, c, "VerticalList.Update", "AbsoluteMax.Height", "h", h.ToString());
}
s = new Vector2(w, h);
Vector2 okSize = c.AmIOkWithThisSize(s);
CUIDebug.Capture(Host, c, "VerticalList.Update", "AmIOkWithThisSize", "s", okSize.ToString());
s = okSize;
if (!c.Fixed) s = new Vector2(s.X, s.Y / c.Scale);
TotalHeight += s.Y;
}
CUIComponentSize size = new CUIComponentSize(c, s);
Sizes.Add(size);
if (c.FillEmptySpace.Y) Resizible.Add(size);
}
TotalHeight += Math.Max(0, Host.Children.Count - 1) * Gap;
sw.Stop();
//CUI.Log($"{Host} vlist measuring {sw.ElapsedMilliseconds}");
float dif = Math.Max(0, Host.Real.Height - TotalHeight);
Resizible.ForEach(c =>
{
c.Size = c.Component.AmIOkWithThisSize(new Vector2(c.Size.X, (float)Math.Round(dif / Resizible.Count)));
//c.Size = new Vector2(c.Size.X, dif / Resizible.Count);
CUIDebug.Capture(Host, c.Component, "VerticalList.Update", "Resizible.ForEach", "c.Size", c.Size.ToString());
});
CUI3DOffset offset = Host.ChildOffsetBounds.Check(Host.ChildrenOffset);
if (Direction == CUIDirection.Straight)
{
float y = 0;
foreach (CUIComponentSize c in Sizes)
{
CUIRect real;
if (Host.ChildrenBoundaries != null) real = Host.ChildrenBoundaries(Host.Real).Check(0, y, c.Size.X, c.Size.Y);
else real = new CUIRect(0, y, c.Size.X, c.Size.Y);
real = offset.Transform(real);
real = real.Shift(Host.Real.Position);
c.Component.SetReal(real, "VerticalList.Update");
y += c.Size.Y + Gap;
}
}
if (Direction == CUIDirection.Reverse)
{
float y = Host.Real.Height;
foreach (CUIComponentSize c in Sizes)
{
y -= c.Size.Y + Gap;
CUIRect real;
if (Host.ChildrenBoundaries != null) real = Host.ChildrenBoundaries(Host.Real).Check(0, y, c.Size.X, c.Size.Y);
else real = new CUIRect(0, y, c.Size.X, c.Size.Y);
real = offset.Transform(real);
real = real.Shift(Host.Real.Position);
c.Component.SetReal(real, "VerticalList.Update");
}
}
}
base.Update();
}
internal override void ResizeToContent()
{
if (AbsoluteChanged && Host.FitContent.X)
{
float tw = 0;
foreach (CUIComponent c in Host.Children)
{
if (c.Ghost.X) continue;
float w = 0;
if (c.Absolute.Width.HasValue) w = c.Absolute.Width.Value;
if (c.AbsoluteMin.Width.HasValue) w = Math.Max(w, c.AbsoluteMin.Width.Value);
else if (c.ForcedMinSize.X.HasValue) w = Math.Max(w, c.ForcedMinSize.X.Value);
if (c.AbsoluteMax.Width.HasValue) w = Math.Min(w, c.AbsoluteMax.Width.Value);
tw = Math.Max(tw, w);
}
CUIDebug.Capture(null, Host, "VerticalList.ResizeToContent", "tw", "ForcedMinSize.W", tw.ToString());
Host.SetForcedMinSize(Host.ForcedMinSize with { X = tw });
}
if (AbsoluteChanged && Host.FitContent.Y)
{
float th = 0;
foreach (CUIComponent c in Host.Children)
{
if (c.Ghost.Y) continue;
float h = 0;
if (!c.FillEmptySpace.Y)
{
if (c.Absolute.Height.HasValue) h = c.Absolute.Height.Value;
if (c.AbsoluteMin.Height.HasValue) h = Math.Max(h, c.AbsoluteMin.Height.Value);
else if (c.ForcedMinSize.Y.HasValue) h = Math.Max(h, c.ForcedMinSize.Y.Value);
if (c.AbsoluteMax.Height.HasValue) h = Math.Min(h, c.AbsoluteMax.Height.Value);
th += h;
}
}
CUIDebug.Capture(null, Host, "VerticalList.ResizeToContent", "th", "ForcedMinSize.Y", th.ToString());
Host.SetForcedMinSize(Host.ForcedMinSize with { Y = th });
}
base.ResizeToContent();
}
public CUILayoutVerticalList() : base() { }
public CUILayoutVerticalList(CUIDirection d, CUIComponent host = null) : base(host)
{
Direction = d;
}
}
}

View File

@@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace QICrabUI
{
/// <summary>
/// Contains all logic for resolving styles in the framework
/// </summary>
public static class CUIGlobalStyleResolver
{
public static void OnComponentStyleChanged(CUIComponent host)
{
CUITypeMetaData meta = CUITypeMetaData.Get(host.GetType());
host.ResolvedStyle = CUIStyle.Merge(meta.ResolvedDefaultStyle, host.Style);
ApplyStyleOn(host.ResolvedStyle, host);
host.InvokeOnStyleApplied();
}
public static void OnComponentStylePropChanged(CUIComponent host, string key)
{
CUITypeMetaData meta = CUITypeMetaData.Get(host.GetType());
if (meta.ResolvedDefaultStyle.Props.ContainsKey(key))
{
host.ResolvedStyle[key] = meta.ResolvedDefaultStyle[key];
}
if (host.Style.Props.ContainsKey(key))
{
host.ResolvedStyle[key] = host.Style[key];
}
ApplyStylePropOn(host.ResolvedStyle, key, host, meta);
host.InvokeOnStyleApplied();
}
public static void OnPaletteChange(CUIPalette palette)
{
foreach (Type CUIType in CUIReflection.CUITypes.Values)
{
foreach (CUIComponent c in CUIComponent.ComponentsByType.GetPage(CUIType))
{
ApplyStyleOn(c.ResolvedStyle, c);
}
}
}
public static void OnDefaultStyleChanged(Type CUIType)
{
try
{
// Merge default styles
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
{
node.Meta.ResolvedDefaultStyle = CUIStyle.Merge(
node.Parent?.Meta.ResolvedDefaultStyle,
node.Meta.DefaultStyle
);
});
// Apply default styles
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
{
foreach (CUIComponent c in CUIComponent.ComponentsByType.GetPage(node.T))
{
OnComponentStyleChanged(c);
}
});
}
catch (Exception e)
{
CUI.Warning($"OnDefaultStyleChanged| {e}");
}
}
public static void OnDefaultStylePropChanged(Type CUIType, string key)
{
try
{
// Merge default styles
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
{
if (node.Parent != null)
{
if (node.Parent.Meta.ResolvedDefaultStyle.Props.ContainsKey(key))
{
node.Meta.ResolvedDefaultStyle[key] = node.Parent.Meta.ResolvedDefaultStyle[key];
}
}
if (node.Meta.DefaultStyle.Props.ContainsKey(key))
{
node.Meta.ResolvedDefaultStyle[key] = node.Meta.DefaultStyle[key];
}
});
// Apply default styles
CUIReflection.CUITypeTree[CUIType].RunRecursive((node) =>
{
foreach (CUIComponent c in CUIComponent.ComponentsByType.GetPage(node.T))
{
OnComponentStylePropChanged(c, key);
}
});
}
catch (Exception e)
{
CUI.Warning(e);
}
}
public static void ApplyStyleOn(CUIStyle style, CUIComponent target)
{
if (target == null)
{
CUI.Warning($"Style target is null");
return;
}
CUITypeMetaData meta = CUITypeMetaData.Get(target.GetType());
foreach (string name in style.Props.Keys)
{
ApplyStylePropOn(style, name, target, meta);
}
}
public static string CUIPalettePrefix = "CUIPalette.";
/// <summary>
/// Applies 1 prop with name on the target
/// </summary>
public static void ApplyStylePropOn(CUIStyle style, string name, CUIComponent target, CUITypeMetaData meta = null)
{
if (target.Unreal) return;
if (target == null) { CUI.Warning($"Style target is null"); return; }
meta ??= CUITypeMetaData.Get(target.GetType());
PropertyInfo pi = meta.Assignable.GetValueOrDefault(name);
if (pi == null)
{
if (CUIPalette.NotifyExcessivePropStyles) CUI.Warning($"Can't apply style: Couldn't find {name} prop in {target}");
return;
}
string raw = style[name];
if (raw.StartsWith(CUIPalettePrefix))
{
PaletteExtractResult result = CUIPalette.Extract(raw.Substring(CUIPalettePrefix.Length), target.Palette);
if (result.Ok)
{
raw = result.Value;
}
else
{
if (CUIPalette.NotifiMissingPropStyles)
{
CUI.Warning($"Can't find {raw.Substring(CUIPalettePrefix.Length)} palette style for {target}");
}
return;
}
}
MethodInfo parse = CUIExtensions.Parse.GetValueOrDefault(pi.PropertyType);
parse ??= pi.PropertyType.GetMethod(
"Parse",
BindingFlags.Public | BindingFlags.Static,
new Type[] { typeof(string) }
);
if (parse == null)
{
CUI.Warning($"Can't parse style prop {name} for {target} because it's type {pi.PropertyType.Name} is missing Parse method");
return;
}
try
{
pi.SetValue(target, parse.Invoke(null, new object[] { raw }));
}
catch (Exception e)
{
CUI.Warning($"Can't parse {raw} into {pi.PropertyType.Name} for {target}");
CUI.Warning(e);
}
}
}
}

View File

@@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using System.Xml.Linq;
namespace QICrabUI
{
/// <summary>
/// In Fact just an observable dict
/// </summary>
public partial class CUIStyle : IEnumerable<KeyValuePair<string, string>>, ICloneable
{
public static CUIStyle DefaultFor(Type T) => CUITypeMetaData.Get(T).DefaultStyle;
public static CUIStyle DefaultFor<T>() where T : CUIComponent => CUITypeMetaData.Get(typeof(T)).DefaultStyle;
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
=> Props.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
=> Props.GetEnumerator();
public void Add(string key, string value)
{
Props[key] = value;
OnPropChanged?.Invoke(key, value);
}
public void Clear() => Props.Clear();
/// <summary>
/// Prop name -> value
/// </summary>
public Dictionary<string, string> Props = new();
public event Action<string, string> OnPropChanged;
public event Action<CUIStyle> OnUse;
public virtual string this[string name]
{
get => Props.ContainsKey(name) ? Props[name] : "";
set => Add(name, value);
}
public object Clone()
{
CUIStyle style = new CUIStyle();
style.Props = new Dictionary<string, string>(Props);
return style;
}
public static CUIStyle Merge(CUIStyle baseStyle, CUIStyle addedStyle)
{
CUIStyle result = new CUIStyle();
if (baseStyle != null)
{
foreach (string key in baseStyle.Props.Keys)
{
result[key] = baseStyle.Props[key];
}
}
if (addedStyle != null)
{
foreach (string key in addedStyle.Props.Keys)
{
result[key] = addedStyle.Props[key];
}
}
return result;
}
public void Use(CUIStyle source)
{
Props = new Dictionary<string, string>(source.Props);
OnUse?.Invoke(this);
}
public void UseString(string raw)
{
Clear();
try
{
string content = raw.Split('{', '}')[1];
if (content.Trim() == "") return;
var pairs = content.Split(',').Select(s => s.Split(':').Select(sub => sub.Trim()).ToArray());
foreach (var pair in pairs)
{
Props[pair[0]] = pair[1];
}
}
catch (Exception e) { CUI.Warning($"Style.UseString failed: {e.Message}"); }
OnUse?.Invoke(this);
}
public void UseXML(XElement element)
{
Clear();
foreach (XElement prop in element.Elements())
{
Props[prop.Name.ToString()] = prop.Value;
}
OnUse?.Invoke(this);
}
public static CUIStyle FromXML(XElement element)
{
CUIStyle style = new CUIStyle();
style.UseXML(element);
return style;
}
public override string ToString()
{
return "{ " + String.Join(", ", Props.Select(kvp => $"{kvp.Key} : {kvp.Value}")) + " }";
}
public static CUIStyle Parse(string raw)
{
CUIStyle style = new CUIStyle();
style.UseString(raw);
return style;
}
public override bool Equals(object obj)
{
if (!(obj is CUIStyle styleB)) return false;
CUIStyle styleA = this;
if (styleA is null && styleB is null) return true;
if (styleA is null || styleB is null) return false;
if (styleA.Props is null || styleB.Props is null) return false;
if (styleA.Props.Count != styleB.Props.Count) return false;
foreach (var (key, value) in styleA.Props)
{
if (!styleB.Props.ContainsKey(key)) return false;
if (styleA[key] != styleB[key]) return false;
}
return true;
}
public static CUIStyle operator +(CUIStyle styleA, CUIStyle styleB) => Merge(styleA, styleB);
public static bool operator ==(CUIStyle styleA, CUIStyle styleB)
{
if (styleA is null && styleB is null) return true;
if (styleA is null || styleB is null) return false;
if (styleA.Props is null || styleB.Props is null) return false;
if (styleA.Props.Count != styleB.Props.Count) return false;
foreach (var (key, value) in styleA.Props)
{
if (!styleB.Props.ContainsKey(key)) return false;
if (styleA[key] != styleB[key]) return false;
}
return true;
}
public static bool operator !=(CUIStyle styleA, CUIStyle styleB)
{
if (styleA is null && styleB is null) return false;
if (styleA is null || styleB is null) return true;
if (styleA.Props is null || styleB.Props is null) return true;
if (styleA.Props.Count != styleB.Props.Count) return true;
foreach (var (key, value) in styleA.Props)
{
if (!styleB.Props.ContainsKey(key)) return true;
if (styleA[key] != styleB[key]) return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.Diagnostics;
namespace QICrabUI
{
public class CUIStyleLoader
{
internal static void InitStatic()
{
CUI.OnInit += () => LoadDefaultStyles();
}
public static string DefaultStylesPath => Path.Combine(CUI.AssetsPath, "Default Styles.xml");
public static void LoadDefaultStyles()
{
Stopwatch sw = Stopwatch.StartNew();
if (CUI.AssetsPath == null) return;
if (!File.Exists(DefaultStylesPath)) return;
Dictionary<Type, CUIStyle> DefaultStyles = new();
XDocument xdoc = XDocument.Load(DefaultStylesPath);
XElement root = xdoc.Element("DefaultStyles");
foreach (XElement componentStyle in root.Elements())
{
Type componentType = CUIReflection.GetComponentTypeByName(componentStyle.Name.ToString());
if (componentType == null)
{
CUI.Warning($"Couldn't find type for default style {componentStyle.Name}");
continue;
}
DefaultStyles[componentType] = CUIStyle.FromXML(componentStyle);
}
sw.Stop();
CUIDebug.Log($"Parsing default styles took {sw.ElapsedMilliseconds}ms");
sw.Restart();
// It's heavy because CUITypeMetaData.Get creates defaults here
foreach (Type T in DefaultStyles.Keys)
{
CUITypeMetaData.Get(T).DefaultStyle = DefaultStyles[T];
}
sw.Stop();
CUIDebug.Log($"Applying default styles took {sw.ElapsedMilliseconds}ms");
}
}
}

Some files were not shown because too many files have changed in this diff Show More