Add quick interactions locally
This commit is contained in:
BIN
Quick Interactions/Assets/CUI.png
(Stored with Git LFS)
Normal file
BIN
Quick Interactions/Assets/CUI.png
(Stored with Git LFS)
Normal file
Binary file not shown.
73
Quick Interactions/Assets/Default Styles.xml
Normal file
73
Quick Interactions/Assets/Default Styles.xml
Normal 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
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
BIN
Quick Interactions/Assets/Interaction icons.png
(Stored with Git LFS)
Normal file
Binary file not shown.
23
Quick Interactions/Assets/PaletteDemo.xml
Normal file
23
Quick Interactions/Assets/PaletteDemo.xml
Normal 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>
|
||||||
55
Quick Interactions/Assets/Palettes/Sets/Blue.xml
Normal file
55
Quick Interactions/Assets/Palettes/Sets/Blue.xml
Normal 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>
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// WIP, can animate any property on any object
|
||||||
|
/// Can run back and forth in [0..1] interval and
|
||||||
|
/// interpolate any property between StartValue and EndValue
|
||||||
|
/// </summary>
|
||||||
|
public class CUIAnimation
|
||||||
|
{
|
||||||
|
internal static void InitStatic()
|
||||||
|
{
|
||||||
|
CUI.OnDispose += () => ActiveAnimations.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HashSet<CUIAnimation> ActiveAnimations = new();
|
||||||
|
/// <summary>
|
||||||
|
/// This is called in CUIUpdate
|
||||||
|
/// </summary>
|
||||||
|
internal static void UpdateAllAnimations(double time)
|
||||||
|
{
|
||||||
|
foreach (CUIAnimation animation in ActiveAnimations)
|
||||||
|
{
|
||||||
|
animation.Step(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Debug { get; set; }
|
||||||
|
public static float StartLambda = 0.0f;
|
||||||
|
public static float EndLambda = 1.0f;
|
||||||
|
|
||||||
|
|
||||||
|
private object target;
|
||||||
|
/// <summary>
|
||||||
|
/// Object containing animated property
|
||||||
|
/// </summary>
|
||||||
|
public object Target
|
||||||
|
{
|
||||||
|
get => target;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
target = value;
|
||||||
|
UpdateSetter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool active;
|
||||||
|
public bool Active
|
||||||
|
{
|
||||||
|
get => active;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Blocked || active == value) return;
|
||||||
|
active = value;
|
||||||
|
|
||||||
|
if (active) ActiveAnimations.Add(this);
|
||||||
|
else ActiveAnimations.Remove(this);
|
||||||
|
ApplyValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In seconds
|
||||||
|
/// </summary>
|
||||||
|
public double Duration
|
||||||
|
{
|
||||||
|
get => 1.0 / Speed * Timing.Step;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
double steps = value / Timing.Step;
|
||||||
|
Speed = 1.0 / steps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double ReverseDuration
|
||||||
|
{
|
||||||
|
get => 1.0 / (BackSpeed ?? 0) * Timing.Step;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
double steps = value / Timing.Step;
|
||||||
|
BackSpeed = 1.0 / steps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will prevent it from starting
|
||||||
|
/// </summary>
|
||||||
|
public bool Blocked { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Progress of animation [0..1]
|
||||||
|
/// </summary>
|
||||||
|
public double Lambda { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Lambda increase per update step, calculated when you set Duration
|
||||||
|
/// </summary>
|
||||||
|
public double Speed { get; set; } = 0.01;
|
||||||
|
public double? BackSpeed { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// If true animation won't stop when reaching end, it will change direction
|
||||||
|
/// </summary>
|
||||||
|
public bool Bounce { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Straight, Reverse
|
||||||
|
/// </summary>
|
||||||
|
public CUIDirection Direction { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Value will be interpolated between these values
|
||||||
|
/// </summary>
|
||||||
|
public object StartValue { get; set; }
|
||||||
|
public object EndValue { get; set; }
|
||||||
|
|
||||||
|
private string property;
|
||||||
|
private Action<object> setter;
|
||||||
|
private Type propertyType;
|
||||||
|
/// <summary>
|
||||||
|
/// Property name that is animated
|
||||||
|
/// </summary>
|
||||||
|
public string Property
|
||||||
|
{
|
||||||
|
get => property;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
property = value;
|
||||||
|
UpdateSetter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<CUIDirection> OnStop;
|
||||||
|
/// <summary>
|
||||||
|
/// You can set custon Interpolate function
|
||||||
|
/// </summary>
|
||||||
|
public Func<float, object> Interpolate
|
||||||
|
{
|
||||||
|
get => interpolate;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
customInterpolate = value;
|
||||||
|
UpdateSetter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Func<float, object> customInterpolate;
|
||||||
|
private Func<float, object> interpolate;
|
||||||
|
//...
|
||||||
|
public Action<object> Convert<T>(Action<T> myActionT)
|
||||||
|
{
|
||||||
|
if (myActionT == null) return null;
|
||||||
|
else return new Action<object>(o => myActionT((T)o));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void UpdateSetter()
|
||||||
|
{
|
||||||
|
if (Target != null && Property != null)
|
||||||
|
{
|
||||||
|
PropertyInfo pi = Target.GetType().GetProperty(Property);
|
||||||
|
if (pi == null)
|
||||||
|
{
|
||||||
|
CUI.Warning($"CUIAnimation couldn't find {Property} in {Target}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyType = pi.PropertyType;
|
||||||
|
|
||||||
|
interpolate = customInterpolate ?? ((l) => CUIInterpolate.Interpolate[propertyType].Invoke(StartValue, EndValue, l));
|
||||||
|
|
||||||
|
|
||||||
|
// https://coub.com/view/1mast0
|
||||||
|
if (propertyType == typeof(float))
|
||||||
|
{
|
||||||
|
setter = Convert<float>(pi.GetSetMethod()?.CreateDelegate<Action<float>>(Target));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyType == typeof(Color))
|
||||||
|
{
|
||||||
|
setter = Convert<Color>(pi.GetSetMethod()?.CreateDelegate<Action<Color>>(Target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resumes animation in the same direction
|
||||||
|
/// </summary>
|
||||||
|
public void Start() => Active = true;
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
Active = false;
|
||||||
|
OnStop?.Invoke(Direction);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Set Direction to Straight and Start
|
||||||
|
/// </summary>
|
||||||
|
public void Forward()
|
||||||
|
{
|
||||||
|
Direction = CUIDirection.Straight;
|
||||||
|
Active = true;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Set Direction to Reverse and Start
|
||||||
|
/// </summary>
|
||||||
|
public void Back()
|
||||||
|
{
|
||||||
|
Direction = CUIDirection.Reverse;
|
||||||
|
Active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set Lambda to 0
|
||||||
|
/// </summary>
|
||||||
|
public void SetToStart() => Lambda = StartLambda;
|
||||||
|
/// <summary>
|
||||||
|
/// Set Lambda to 1
|
||||||
|
/// </summary>
|
||||||
|
public void SetToEnd() => Lambda = EndLambda;
|
||||||
|
|
||||||
|
|
||||||
|
private void UpdateState()
|
||||||
|
{
|
||||||
|
if (Direction == CUIDirection.Straight && Lambda >= EndLambda)
|
||||||
|
{
|
||||||
|
Lambda = EndLambda;
|
||||||
|
if (Bounce) Direction = CUIDirection.Reverse;
|
||||||
|
else Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Direction == CUIDirection.Reverse && Lambda <= StartLambda)
|
||||||
|
{
|
||||||
|
Lambda = StartLambda;
|
||||||
|
if (Bounce) Direction = CUIDirection.Straight;
|
||||||
|
else Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyValue()
|
||||||
|
{
|
||||||
|
if (interpolate == null) return;
|
||||||
|
object value = interpolate.Invoke((float)Lambda);
|
||||||
|
setter?.Invoke(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Step(double time)
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
ApplyValue();
|
||||||
|
Lambda += Direction == CUIDirection.Straight ? Speed : -(BackSpeed ?? Speed);
|
||||||
|
if (Debug) LogStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogStatus() => CUI.Log($"Active:{Active} Direction:{Direction} Lambda:{Lambda}");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class containing a few interpolate functions for CUIAnimation
|
||||||
|
/// </summary>
|
||||||
|
public class CUIInterpolate
|
||||||
|
{
|
||||||
|
public static object InterpolateColor(object start, object end, double lambda)
|
||||||
|
{
|
||||||
|
return ((Color)start).To(((Color)end), (float)lambda);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object InterpolateVector2(object start, object end, double lambda)
|
||||||
|
{
|
||||||
|
Vector2 a = (Vector2)start;
|
||||||
|
Vector2 b = (Vector2)end;
|
||||||
|
return a + (b - a) * (float)lambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object InterpolateFloat(object start, object end, double lambda)
|
||||||
|
{
|
||||||
|
float a = (float)start;
|
||||||
|
float b = (float)end;
|
||||||
|
return a + (b - a) * (float)lambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<Type, Func<object, object, double, object>> Interpolate = new();
|
||||||
|
|
||||||
|
internal static void InitStatic()
|
||||||
|
{
|
||||||
|
CUI.OnInit += () =>
|
||||||
|
{
|
||||||
|
Interpolate[typeof(Color)] = InterpolateColor;
|
||||||
|
Interpolate[typeof(Vector2)] = InterpolateVector2;
|
||||||
|
Interpolate[typeof(float)] = InterpolateFloat;
|
||||||
|
};
|
||||||
|
|
||||||
|
CUI.OnDispose += () =>
|
||||||
|
{
|
||||||
|
Interpolate.Clear();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
301
Quick Interactions/CSharp/Client/CrabUI/CUI.cs
Normal file
301
Quick Interactions/CSharp/Client/CrabUI/CUI.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
208
Quick Interactions/CSharp/Client/CrabUI/Changelog.md
Normal file
208
Quick Interactions/CSharp/Client/CrabUI/Changelog.md
Normal 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
|
||||||
115
Quick Interactions/CSharp/Client/CrabUI/Components/CUIButton.cs
Normal file
115
Quick Interactions/CSharp/Client/CrabUI/Components/CUIButton.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
105
Quick Interactions/CSharp/Client/CrabUI/Components/CUICanvas.cs
Normal file
105
Quick Interactions/CSharp/Client/CrabUI/Components/CUICanvas.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent : IDisposable
|
||||||
|
{
|
||||||
|
private void SetupAnimations()
|
||||||
|
{
|
||||||
|
Animations = new Indexer<string, CUIAnimation>(
|
||||||
|
(key) => animations.GetValueOrDefault(key),
|
||||||
|
(key, value) => AddAnimation(key, value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private Dictionary<string, CUIAnimation> animations = new();
|
||||||
|
public Indexer<string, CUIAnimation> Animations;
|
||||||
|
public void AddAnimation(string name, CUIAnimation animation)
|
||||||
|
{
|
||||||
|
animation.Target = this;
|
||||||
|
animations[name] = animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BlockChildrenAnimations()
|
||||||
|
{
|
||||||
|
foreach (CUIComponent child in Children)
|
||||||
|
{
|
||||||
|
foreach (CUIAnimation animation in child.animations.Values)
|
||||||
|
{
|
||||||
|
animation.Stop();
|
||||||
|
animation.Blocked = true;
|
||||||
|
}
|
||||||
|
child.BlockChildrenAnimations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Just a wrapper for CUIProps
|
||||||
|
/// idk how to separate them better
|
||||||
|
/// </summary>
|
||||||
|
//TODO this should be a dict, and cuiprop should have hash
|
||||||
|
public CUIComponentProps CUIProps { get; set; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
public class CUIComponentProps
|
||||||
|
{
|
||||||
|
public CUIProp<int?> ZIndex = new CUIProp<int?>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
foreach (var child in host.Children)
|
||||||
|
{
|
||||||
|
//HACK think, should i propagate null?
|
||||||
|
if (v.HasValue && !child.IgnoreParentZIndex)
|
||||||
|
{
|
||||||
|
child.ZIndex = v.Value + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<bool> IgnoreEvents = new CUIProp<bool>()
|
||||||
|
{
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
foreach (var child in host.Children)
|
||||||
|
{
|
||||||
|
if (!child.IgnoreParentEventIgnorance) child.IgnoreEvents = v;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<bool> Visible = new CUIProp<bool>()
|
||||||
|
{
|
||||||
|
Value = true,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
foreach (var child in host.Children)
|
||||||
|
{
|
||||||
|
if (!child.IgnoreParentVisibility) child.Visible = v;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<bool> Revealed = new CUIProp<bool>()
|
||||||
|
{
|
||||||
|
Value = true,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
// host.TreeChanged = true;
|
||||||
|
host.Visible = v;
|
||||||
|
host.IgnoreEvents = !v;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUIBool2> Ghost = new CUIProp<CUIBool2>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
AbsoluteProp = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<bool> CullChildren = new CUIProp<bool>()
|
||||||
|
{
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
host.HideChildrenOutsideFrame = v;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUI3DOffset> ChildrenOffset = new CUIProp<CUI3DOffset>()
|
||||||
|
{
|
||||||
|
ChildProp = true,
|
||||||
|
Value = new CUI3DOffset(0, 0, 1), // uuuuuuuuu suka blyat!
|
||||||
|
Validate = (v, host) => host.ChildOffsetBounds.Check(v),
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
foreach (var child in host.Children)
|
||||||
|
{
|
||||||
|
if (!child.Fixed) child.Scale = v.Z;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<bool> ResizeToSprite = new CUIProp<bool>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
if (v)
|
||||||
|
{
|
||||||
|
host.Absolute = host.Absolute with
|
||||||
|
{
|
||||||
|
Width = host.BackgroundSprite.SourceRect.Width,
|
||||||
|
Height = host.BackgroundSprite.SourceRect.Height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public CUIProp<CUIBool2> FillEmptySpace = new CUIProp<CUIBool2>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUIBool2> FitContent = new CUIProp<CUIBool2>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
AbsoluteProp = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUINullRect> Absolute = new CUIProp<CUINullRect>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
AbsoluteProp = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUINullRect> AbsoluteMin = new CUIProp<CUINullRect>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
AbsoluteProp = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUINullRect> AbsoluteMax = new CUIProp<CUINullRect>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
AbsoluteProp = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUINullRect> Relative = new CUIProp<CUINullRect>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
};
|
||||||
|
public CUIProp<CUINullRect> RelativeMin = new CUIProp<CUINullRect>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
};
|
||||||
|
public CUIProp<CUINullRect> RelativeMax = new CUIProp<CUINullRect>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
};
|
||||||
|
public CUIProp<CUINullRect> CrossRelative = new CUIProp<CUINullRect>()
|
||||||
|
{
|
||||||
|
LayoutProp = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
#region Graphic Props --------------------------------------------------------
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
public CUIProp<PaletteOrder> Palette = new CUIProp<PaletteOrder>()
|
||||||
|
{
|
||||||
|
ShowInDebug = false,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
//TODO should this be called in deserialize?
|
||||||
|
CUIGlobalStyleResolver.OnComponentStyleChanged(host);
|
||||||
|
// foreach (var child in host.Children)
|
||||||
|
// {
|
||||||
|
// child.Palette = v;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<CUISprite> BackgroundSprite = new CUIProp<CUISprite>()
|
||||||
|
{
|
||||||
|
Value = CUISprite.Default,
|
||||||
|
ShowInDebug = false,
|
||||||
|
Validate = (v, host) => v ?? CUISprite.Default,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
if (host.ResizeToSprite)
|
||||||
|
{
|
||||||
|
host.Absolute = host.Absolute with
|
||||||
|
{
|
||||||
|
Width = v.SourceRect.Width,
|
||||||
|
Height = v.SourceRect.Height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host.IgnoreTransparent)
|
||||||
|
{
|
||||||
|
Rectangle bounds = host.BackgroundSprite.Texture.Bounds;
|
||||||
|
host.TextureData = new Color[bounds.Width * bounds.Height];
|
||||||
|
host.BackgroundSprite.Texture.GetData<Color>(host.TextureData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<bool> IgnoreTransparent = new CUIProp<bool>()
|
||||||
|
{
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
if (v)
|
||||||
|
{
|
||||||
|
Rectangle bounds = host.BackgroundSprite.Texture.Bounds;
|
||||||
|
host.TextureData = new Color[bounds.Width * bounds.Height];
|
||||||
|
host.BackgroundSprite.Texture.GetData<Color>(host.TextureData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
host.TextureData = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<Color> BackgroundColor = new CUIProp<Color>()
|
||||||
|
{
|
||||||
|
ShowInDebug = false,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
host.BackgroundVisible = v != Color.Transparent;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<Color> OutlineColor = new CUIProp<Color>()
|
||||||
|
{
|
||||||
|
ShowInDebug = false,
|
||||||
|
OnSet = (v, host) =>
|
||||||
|
{
|
||||||
|
host.OutlineVisible = v != Color.Transparent;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public CUIProp<Vector2> Padding = new CUIProp<Vector2>()
|
||||||
|
{
|
||||||
|
Value = new Vector2(2, 2),
|
||||||
|
DecorProp = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Global ID, unique for component
|
||||||
|
/// </summary>
|
||||||
|
public int ID { get; set; }
|
||||||
|
|
||||||
|
internal bool DebugHighlight { get; set; }
|
||||||
|
|
||||||
|
private CUIMainComponent mainComponent;
|
||||||
|
/// <summary>
|
||||||
|
/// Link to CUIMainComponent, passed to children
|
||||||
|
/// </summary>
|
||||||
|
public CUIMainComponent MainComponent
|
||||||
|
{
|
||||||
|
get => mainComponent;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mainComponent = value;
|
||||||
|
foreach (var child in Children) { child.MainComponent = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int positionalZIndex;
|
||||||
|
internal int addedZIndex;
|
||||||
|
|
||||||
|
[Calculated] public bool Focused { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True when parent has HideChildrenOutsideFrame and child wanders beyond parents border
|
||||||
|
/// </summary>
|
||||||
|
[Calculated] internal bool CulledOut { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BackgroundColor != Color.Transparent
|
||||||
|
/// </summary>
|
||||||
|
protected bool BackgroundVisible { get; set; }
|
||||||
|
|
||||||
|
protected bool OutlineVisible { get; set; }
|
||||||
|
|
||||||
|
// This is for state clones, to protect them from style changes
|
||||||
|
internal bool Unreal { get; set; }
|
||||||
|
|
||||||
|
public bool MouseOver { get; set; }
|
||||||
|
public bool MousePressed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used by text to prevent resizing beyond that
|
||||||
|
/// and works as AbsoluteMin
|
||||||
|
/// </summary>
|
||||||
|
[Calculated]
|
||||||
|
public CUINullVector2 ForcedMinSize
|
||||||
|
{
|
||||||
|
get => forsedSize;
|
||||||
|
set => SetForcedMinSize(value);
|
||||||
|
}
|
||||||
|
protected CUINullVector2 forsedSize; internal void SetForcedMinSize(CUINullVector2 value, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
forsedSize = value;
|
||||||
|
CUIDebug.Capture(null, this, "SetForcedMinSize", memberName, "ForcedMinSize", ForcedMinSize.ToString());
|
||||||
|
OnPropChanged();//TODO this is the reason why lists with a lot of children lag
|
||||||
|
//OnSelfAndParentChanged();
|
||||||
|
OnAbsolutePropChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is set by ChildrenOffset when zooming, and iirc consumed by text to adjust text scale
|
||||||
|
/// </summary>
|
||||||
|
[Calculated]
|
||||||
|
public float Scale
|
||||||
|
{
|
||||||
|
get => scale;
|
||||||
|
set => SetScale(value);
|
||||||
|
}
|
||||||
|
protected float scale = 1f; internal void SetScale(float value, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
scale = value;
|
||||||
|
foreach (var child in Children) { child.Scale = value; }
|
||||||
|
// OnDecorPropChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculated Prop, Real + BorderThickness
|
||||||
|
/// </summary>
|
||||||
|
protected CUIRect BorderBox { get; set; }
|
||||||
|
protected CUIRect OutlineBox { get; set; }
|
||||||
|
internal Rectangle? ScissorRect { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer for texture data, for IgnoreTransparent checks
|
||||||
|
/// </summary>
|
||||||
|
protected Color[] TextureData;
|
||||||
|
/// <summary>
|
||||||
|
/// Calculated prop, position on real screen in pixels
|
||||||
|
/// Should be fully calculated after CUIMainComponent.Update
|
||||||
|
/// </summary>
|
||||||
|
[Calculated]
|
||||||
|
public CUIRect Real
|
||||||
|
{
|
||||||
|
get => real;
|
||||||
|
set => SetReal(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private CUIRect real; internal void SetReal(CUIRect value, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
//HACK idk if i need it
|
||||||
|
real = new CUIRect(
|
||||||
|
(float)Math.Round(value.Left),
|
||||||
|
(float)Math.Round(value.Top),
|
||||||
|
(float)Math.Round(value.Width),
|
||||||
|
(float)Math.Round(value.Height)
|
||||||
|
);
|
||||||
|
// real = value;
|
||||||
|
CUIDebug.Capture(null, this, "SetReal", memberName, "real", real.ToString());
|
||||||
|
|
||||||
|
|
||||||
|
BorderBox = real;
|
||||||
|
// BorderBox = new CUIRect(
|
||||||
|
// real.Left - BorderThickness,
|
||||||
|
// real.Top - BorderThickness,
|
||||||
|
// real.Width + 2 * BorderThickness,
|
||||||
|
// real.Height + 2 * BorderThickness
|
||||||
|
// );
|
||||||
|
|
||||||
|
OutlineBox = new CUIRect(
|
||||||
|
real.Left - OutlineThickness,
|
||||||
|
real.Top - OutlineThickness,
|
||||||
|
real.Width + 2 * OutlineThickness,
|
||||||
|
real.Height + 2 * OutlineThickness
|
||||||
|
);
|
||||||
|
|
||||||
|
if (HideChildrenOutsideFrame)
|
||||||
|
{
|
||||||
|
Rectangle SRect = real.Box;
|
||||||
|
|
||||||
|
// //HACK Remove these + 1
|
||||||
|
// Rectangle SRect = new Rectangle(
|
||||||
|
// (int)real.Left + 1,
|
||||||
|
// (int)real.Top + 1,
|
||||||
|
// (int)real.Width - 2,
|
||||||
|
// (int)real.Height - 2
|
||||||
|
// );
|
||||||
|
|
||||||
|
if (Parent?.ScissorRect != null)
|
||||||
|
{
|
||||||
|
ScissorRect = Rectangle.Intersect(Parent.ScissorRect.Value, SRect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ScissorRect = SRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else ScissorRect = Parent?.ScissorRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
|
||||||
|
public class CommandAttribute : System.Attribute { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can be dispatched up the component tree to notify parent about something
|
||||||
|
/// add pass some event data without creating a hard link
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Name"></param>
|
||||||
|
public record CUICommand(string Name, object Data = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can be dispatched down the component tree to pass some data to the children
|
||||||
|
/// without creating a hard link
|
||||||
|
/// </summary>
|
||||||
|
public record CUIData(string Name, object Data = null);
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
private void SetupCommands()
|
||||||
|
{
|
||||||
|
// This is actually expensive
|
||||||
|
//AddCommands();
|
||||||
|
OnTreeChanged += UpdateDataTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This command will be dispatched up when some component specific event happens
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string Command { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// this will be executed on any command
|
||||||
|
/// </summary>
|
||||||
|
public event Action<CUICommand> OnAnyCommand;
|
||||||
|
/// <summary>
|
||||||
|
/// Will be executed when receiving any data
|
||||||
|
/// </summary>
|
||||||
|
public event Action<CUIData> OnAnyData;
|
||||||
|
/// <summary>
|
||||||
|
/// Happens when appropriate data is received
|
||||||
|
/// </summary>
|
||||||
|
public event Action<Object> OnConsume;
|
||||||
|
/// <summary>
|
||||||
|
/// Will consume data with this name
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string Consumes { get; set; }
|
||||||
|
|
||||||
|
private bool reflectCommands;
|
||||||
|
[CUISerializable]
|
||||||
|
public bool ReflectCommands
|
||||||
|
{
|
||||||
|
get => reflectCommands;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
reflectCommands = value;
|
||||||
|
OnAnyCommand += (command) =>
|
||||||
|
{
|
||||||
|
foreach (CUIComponent child in Children)
|
||||||
|
{
|
||||||
|
child.DispatchDown(new CUIData(command.Name, command.Data));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool retranslateCommands;
|
||||||
|
[CUISerializable]
|
||||||
|
public bool RetranslateCommands
|
||||||
|
{
|
||||||
|
get => retranslateCommands;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
retranslateCommands = value;
|
||||||
|
OnAnyCommand += (command) =>
|
||||||
|
{
|
||||||
|
Parent?.DispatchUp(command);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optimization to data flow
|
||||||
|
/// If not empty component will search for consumers of the data
|
||||||
|
/// and pass it directly to them instead of broadcasting it
|
||||||
|
/// </summary>
|
||||||
|
//[CUISerializable]
|
||||||
|
public ObservableCollection<string> Emits
|
||||||
|
{
|
||||||
|
get => emits;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
emits = value;
|
||||||
|
emits.CollectionChanged += (o, e) => UpdateDataTargets();
|
||||||
|
UpdateDataTargets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ObservableCollection<string> emits = new();
|
||||||
|
|
||||||
|
private void UpdateDataTargets()
|
||||||
|
{
|
||||||
|
if (Emits.Count > 0)
|
||||||
|
{
|
||||||
|
DataTargets.Clear();
|
||||||
|
|
||||||
|
RunRecursiveOn(this, (c) =>
|
||||||
|
{
|
||||||
|
if (Emits.Contains(c.Consumes))
|
||||||
|
{
|
||||||
|
if (!DataTargets.ContainsKey(c.Consumes)) DataTargets[c.Consumes] = new();
|
||||||
|
DataTargets[c.Consumes].Add(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Consumers of emmited data, updates on tree change
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, List<CUIComponent>> DataTargets = new();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All commands
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, Action<object>> Commands { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manually adds command
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
public void AddCommand(string name, Action<object> action) => Commands.Add(name, action);
|
||||||
|
public void RemoveCommand(string name) => Commands.Remove(name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executed autpmatically on component creation
|
||||||
|
/// Methods ending in "Command" will be added as commands
|
||||||
|
/// </summary>
|
||||||
|
private void AddCommands()
|
||||||
|
{
|
||||||
|
foreach (MethodInfo mi in this.GetType().GetMethods())
|
||||||
|
{
|
||||||
|
if (Attribute.IsDefined(mi, typeof(CommandAttribute)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string name = mi.Name;
|
||||||
|
if (name != "Command" && name.EndsWith("Command"))
|
||||||
|
{
|
||||||
|
name = name.Substring(0, name.Length - "Command".Length);
|
||||||
|
}
|
||||||
|
AddCommand(name, mi.CreateDelegate<Action<object>>(this));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Info($"{e.Message}\nMethod: {this.GetType()}.{mi.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispathes command up the component tree until someone consumes it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
public void DispatchUp(CUICommand command)
|
||||||
|
{
|
||||||
|
if (OnAnyCommand != null) OnAnyCommand?.Invoke(command);
|
||||||
|
else if (Commands.ContainsKey(command.Name)) Execute(command);
|
||||||
|
else Parent?.DispatchUp(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispathes command down the component tree until someone consumes it
|
||||||
|
/// </summary>
|
||||||
|
public void DispatchDown(CUIData data)
|
||||||
|
{
|
||||||
|
if (Emits.Contains(data.Name))
|
||||||
|
{
|
||||||
|
if (DataTargets.ContainsKey(data.Name))
|
||||||
|
{
|
||||||
|
foreach (CUIComponent target in DataTargets[data.Name])
|
||||||
|
{
|
||||||
|
target.OnConsume?.Invoke(data.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Consumes == data.Name) OnConsume?.Invoke(data.Data);
|
||||||
|
else if (OnAnyData != null) OnAnyData.Invoke(data);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (CUIComponent child in Children) child.DispatchDown(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will execute action corresponding to this command
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandName"></param>
|
||||||
|
public void Execute(CUICommand command)
|
||||||
|
{
|
||||||
|
Commands.GetValueOrDefault(command.Name)?.Invoke(command.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
#region Debug --------------------------------------------------------
|
||||||
|
/// <summary>
|
||||||
|
/// Mark component and its children for debug
|
||||||
|
/// Used in debug interface
|
||||||
|
/// </summary>
|
||||||
|
private bool debug; public bool Debug
|
||||||
|
{
|
||||||
|
get => debug;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
debug = value;
|
||||||
|
//foreach (CUIComponent c in Children) { c.Debug = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For debug frame itself
|
||||||
|
/// </summary>
|
||||||
|
private bool ignoreDebug; public bool IgnoreDebug
|
||||||
|
{
|
||||||
|
get => ignoreDebug;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ignoreDebug = value;
|
||||||
|
foreach (CUIComponent c in Children) { c.IgnoreDebug = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintTree(string offset = "")
|
||||||
|
{
|
||||||
|
CUI.Log($"{offset}{this}");
|
||||||
|
foreach (CUIComponent child in Children)
|
||||||
|
{
|
||||||
|
child.PrintTree(offset + "| ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prints component and then message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="source"></param>
|
||||||
|
/// <param name="lineNumber"></param>
|
||||||
|
public void Info(object msg, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||||
|
{
|
||||||
|
var fi = new FileInfo(source);
|
||||||
|
|
||||||
|
CUI.Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", Color.Yellow * 0.5f);
|
||||||
|
CUI.Log($"{this} {msg ?? "null"}", Color.Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region AKA --------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent can memorize it's children by their names, AKA
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string AKA { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// All memorized components
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, CUIComponent> NamedComponents { get; set; } = new();
|
||||||
|
|
||||||
|
public CUIComponent Remember(CUIComponent c, string name)
|
||||||
|
{
|
||||||
|
NamedComponents[name] = c;
|
||||||
|
c.AKA = name;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// If it already has AKA
|
||||||
|
/// </summary>
|
||||||
|
public CUIComponent Remember(CUIComponent c)
|
||||||
|
{
|
||||||
|
if (c.AKA != null) NamedComponents[c.AKA] = c;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIComponent Forget(string name)
|
||||||
|
{
|
||||||
|
if (name == null) return null;
|
||||||
|
CUIComponent c = NamedComponents.GetValueOrDefault(name);
|
||||||
|
NamedComponents.Remove(name);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// If it already has AKA
|
||||||
|
/// </summary>
|
||||||
|
public CUIComponent Forget(CUIComponent c)
|
||||||
|
{
|
||||||
|
if (c?.AKA != null) NamedComponents.Remove(c.AKA);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// You can access NamedComponents with this indexer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CUIComponent this[string name]
|
||||||
|
{
|
||||||
|
get => Get(name);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.Parent != null) Remember(value, name);
|
||||||
|
else Append(value, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns memorized component by name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual CUIComponent Get(string name)
|
||||||
|
{
|
||||||
|
if (name == null) return null;
|
||||||
|
if (NamedComponents.ContainsKey(name)) return NamedComponents[name];
|
||||||
|
|
||||||
|
CUIComponent component = this;
|
||||||
|
string[] names = name.Split('.');
|
||||||
|
|
||||||
|
foreach (string n in names)
|
||||||
|
{
|
||||||
|
component = component.NamedComponents.GetValueOrDefault(n);
|
||||||
|
|
||||||
|
if (component == null)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Failed to Get {name} from {this}, there's no {n}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
public T Get<T>(string name) where T : CUIComponent => (T)Get(name);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
#region Events --------------------------------------------------------
|
||||||
|
|
||||||
|
[CUISerializable] public bool ConsumeMouseClicks { get; set; }
|
||||||
|
[CUISerializable] public bool ConsumeDragAndDrop { get; set; }
|
||||||
|
[CUISerializable] public bool ConsumeSwipe { get; set; }
|
||||||
|
[CUISerializable] public bool ConsumeMouseScroll { get; set; }
|
||||||
|
|
||||||
|
//HACK no one will ever find it, hehehe
|
||||||
|
public void CascadeRefresh()
|
||||||
|
{
|
||||||
|
if (this is IRefreshable refreshable) refreshable.Refresh();
|
||||||
|
Children.ForEach(c => c.CascadeRefresh());
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action OnTreeChanged;
|
||||||
|
public event Action<double> OnUpdate;
|
||||||
|
public event Action<CUIInput> OnMouseLeave;
|
||||||
|
public event Action<CUIInput> OnMouseEnter;
|
||||||
|
public event Action<CUIInput> OnMouseDown;
|
||||||
|
public event Action<CUIInput> OnMouseUp;
|
||||||
|
public event Action<CUIInput> OnMouseMove;
|
||||||
|
public event Action<CUIInput> OnMouseOn;
|
||||||
|
public event Action<CUIInput> OnMouseOff;
|
||||||
|
public event Action<CUIInput> OnClick;
|
||||||
|
public event Action<CUIInput> OnDClick;
|
||||||
|
public event Action<CUIInput> OnScroll;
|
||||||
|
public event Action<float, float> OnDrag;
|
||||||
|
public event Action<float, float> OnSwipe;
|
||||||
|
public event Action<CUIInput> OnKeyDown;
|
||||||
|
public event Action<CUIInput> OnKeyUp;
|
||||||
|
public event Action<CUIInput> OnTextInput;
|
||||||
|
public event Action OnFocus;
|
||||||
|
public event Action OnFocusLost;
|
||||||
|
|
||||||
|
|
||||||
|
public Action<double> AddOnUpdate { set { OnUpdate += value; } }
|
||||||
|
public Action<CUIInput> AddOnMouseLeave { set { OnMouseLeave += value; } }
|
||||||
|
public Action<CUIInput> AddOnMouseEnter { set { OnMouseEnter += value; } }
|
||||||
|
public Action<CUIInput> AddOnMouseDown { set { OnMouseDown += value; } }
|
||||||
|
public Action<CUIInput> AddOnMouseUp { set { OnMouseUp += value; } }
|
||||||
|
public Action<CUIInput> AddOnMouseMove { set { OnMouseMove += value; } }
|
||||||
|
public Action<CUIInput> AddOnMouseOn { set { OnMouseOn += value; } }
|
||||||
|
public Action<CUIInput> AddOnMouseOff { set { OnMouseOff += value; } }
|
||||||
|
public Action<CUIInput> AddOnClick { set { OnClick += value; } }
|
||||||
|
public Action<CUIInput> AddOnDClick { set { OnDClick += value; } }
|
||||||
|
public Action<CUIInput> AddOnScroll { set { OnScroll += value; } }
|
||||||
|
public Action<float, float> AddOnDrag { set { OnDrag += value; } }
|
||||||
|
public Action<float, float> AddOnSwipe { set { OnSwipe += value; } }
|
||||||
|
public Action<CUIInput> AddOnKeyDown { set { OnKeyDown += value; } }
|
||||||
|
public Action<CUIInput> AddOnKeyUp { set { OnKeyUp += value; } }
|
||||||
|
public Action<CUIInput> AddOnTextInput { set { OnTextInput += value; } }
|
||||||
|
public Action AddOnFocus { set { OnFocus += value; } }
|
||||||
|
public Action AddOnFocusLost { set { OnFocusLost += value; } }
|
||||||
|
|
||||||
|
//TODO add more CUISpriteDrawModes
|
||||||
|
public virtual bool IsPointOnTransparentPixel(Vector2 point)
|
||||||
|
{
|
||||||
|
if (BackgroundSprite.DrawMode != CUISpriteDrawMode.Resize) return true;
|
||||||
|
|
||||||
|
//TODO hangle case where offset != sprite.origin
|
||||||
|
Vector2 RotationCenter = new Vector2(
|
||||||
|
BackgroundSprite.Offset.X * Real.Width,
|
||||||
|
BackgroundSprite.Offset.Y * Real.Height
|
||||||
|
);
|
||||||
|
|
||||||
|
Vector2 v = (point - Real.Position - RotationCenter).Rotate(-BackgroundSprite.Rotation) + RotationCenter;
|
||||||
|
|
||||||
|
float x = v.X / Real.Width;
|
||||||
|
float y = v.Y / Real.Height;
|
||||||
|
|
||||||
|
Rectangle bounds = BackgroundSprite.Texture.Bounds;
|
||||||
|
Rectangle SourceRect = BackgroundSprite.SourceRect;
|
||||||
|
|
||||||
|
int textureX = (int)Math.Round(SourceRect.X + x * SourceRect.Width);
|
||||||
|
int textureY = (int)Math.Round(SourceRect.Y + y * SourceRect.Height);
|
||||||
|
|
||||||
|
if (textureX < SourceRect.X || (SourceRect.X + SourceRect.Width - 1) < textureX) return true;
|
||||||
|
if (textureY < SourceRect.Y || (SourceRect.Y + SourceRect.Height - 1) < textureY) return true;
|
||||||
|
|
||||||
|
Color cl = TextureData[textureY * bounds.Width + textureX];
|
||||||
|
|
||||||
|
return cl.A == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual bool ShouldInvoke(CUIInput e)
|
||||||
|
{
|
||||||
|
if (IgnoreTransparent)
|
||||||
|
{
|
||||||
|
return !IsPointOnTransparentPixel(e.MousePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InvokeOnUpdate(double totalTime) => OnUpdate?.Invoke(totalTime);
|
||||||
|
internal void InvokeOnMouseLeave(CUIInput e) { OnMouseLeave?.Invoke(e); }
|
||||||
|
internal void InvokeOnMouseEnter(CUIInput e) { if (ShouldInvoke(e)) OnMouseEnter?.Invoke(e); }
|
||||||
|
internal void InvokeOnMouseDown(CUIInput e) { if (ShouldInvoke(e)) OnMouseDown?.Invoke(e); }
|
||||||
|
internal void InvokeOnMouseUp(CUIInput e) { if (ShouldInvoke(e)) OnMouseUp?.Invoke(e); }
|
||||||
|
internal void InvokeOnMouseMove(CUIInput e) { if (ShouldInvoke(e)) OnMouseMove?.Invoke(e); }
|
||||||
|
internal void InvokeOnMouseOn(CUIInput e) { if (ShouldInvoke(e)) OnMouseOn?.Invoke(e); }
|
||||||
|
internal void InvokeOnMouseOff(CUIInput e) { if (ShouldInvoke(e)) OnMouseOff?.Invoke(e); }
|
||||||
|
internal void InvokeOnClick(CUIInput e) { if (ShouldInvoke(e)) OnClick?.Invoke(e); }
|
||||||
|
internal void InvokeOnDClick(CUIInput e) { if (ShouldInvoke(e)) OnDClick?.Invoke(e); }
|
||||||
|
internal void InvokeOnScroll(CUIInput e) { if (ShouldInvoke(e)) OnScroll?.Invoke(e); }
|
||||||
|
internal void InvokeOnDrag(float x, float y) => OnDrag?.Invoke(x, y);
|
||||||
|
internal void InvokeOnSwipe(float x, float y) => OnSwipe?.Invoke(x, y);
|
||||||
|
internal void InvokeOnKeyDown(CUIInput e) { if (ShouldInvoke(e)) OnKeyDown?.Invoke(e); }
|
||||||
|
internal void InvokeOnKeyUp(CUIInput e) { if (ShouldInvoke(e)) OnKeyUp?.Invoke(e); }
|
||||||
|
internal void InvokeOnTextInput(CUIInput e) { if (ShouldInvoke(e)) OnTextInput?.Invoke(e); }
|
||||||
|
internal void InvokeOnFocus() => OnFocus?.Invoke();
|
||||||
|
internal void InvokeOnFocusLost() => OnFocusLost?.Invoke();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Handles --------------------------------------------------------
|
||||||
|
|
||||||
|
internal CUIDragHandle DragHandle = new CUIDragHandle();
|
||||||
|
[CUISerializable]
|
||||||
|
public bool Draggable
|
||||||
|
{
|
||||||
|
get => DragHandle.Draggable;
|
||||||
|
set => DragHandle.Draggable = value;
|
||||||
|
}
|
||||||
|
//HACK Do i really need this?
|
||||||
|
internal CUIFocusHandle FocusHandle = new CUIFocusHandle();
|
||||||
|
[CUISerializable]
|
||||||
|
public bool Focusable
|
||||||
|
{
|
||||||
|
get => FocusHandle.Focusable;
|
||||||
|
set => FocusHandle.Focusable = value;
|
||||||
|
}
|
||||||
|
public CUIResizeHandle LeftResizeHandle = new CUIResizeHandle(new Vector2(0, 1), new CUIBool2(false, false));
|
||||||
|
public CUIResizeHandle RightResizeHandle = new CUIResizeHandle(new Vector2(1, 1), new CUIBool2(true, false));
|
||||||
|
public bool Resizible
|
||||||
|
{
|
||||||
|
get => ResizibleLeft || ResizibleRight;
|
||||||
|
set { ResizibleLeft = value; ResizibleRight = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public bool ResizibleLeft
|
||||||
|
{
|
||||||
|
get => LeftResizeHandle.Visible;
|
||||||
|
set => LeftResizeHandle.Visible = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public bool ResizibleRight
|
||||||
|
{
|
||||||
|
get => RightResizeHandle.Visible;
|
||||||
|
set => RightResizeHandle.Visible = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public CUIBool2 ResizeDirection
|
||||||
|
{
|
||||||
|
get => RightResizeHandle.Direction;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
LeftResizeHandle.Direction = value;
|
||||||
|
RightResizeHandle.Direction = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal CUISwipeHandle SwipeHandle = new CUISwipeHandle();
|
||||||
|
[CUISerializable]
|
||||||
|
public bool Swipeable
|
||||||
|
{
|
||||||
|
get => SwipeHandle.Swipeable;
|
||||||
|
set => SwipeHandle.Swipeable = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used for text, should be in CUITextBlock really
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public Vector2 Padding
|
||||||
|
{
|
||||||
|
get => CUIProps.Padding.Value;
|
||||||
|
set => CUIProps.Padding.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Should be one texture, not sprite sheet
|
||||||
|
/// Or there would be no way to wrap it
|
||||||
|
/// Top side will always point outwards
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUISprite BorderSprite { get; set; } = CUISprite.Default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container for Color and Thickness
|
||||||
|
/// Border is drawn inside the component and will eat space from content
|
||||||
|
/// If "by side" border prop != null then it'll take presidence
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public CUIBorder Border { get; set; } = new CUIBorder();
|
||||||
|
[CUISerializable] public CUIBorder TopBorder { get; set; }
|
||||||
|
[CUISerializable] public CUIBorder RigthBorder { get; set; }
|
||||||
|
[CUISerializable] public CUIBorder BottomBorder { get; set; }
|
||||||
|
[CUISerializable] public CUIBorder LeftBorder { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public float OutlineThickness { get; set; } = 1f;
|
||||||
|
/// <summary>
|
||||||
|
/// Outline is like a border, but on the outside of the component
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public Color OutlineColor
|
||||||
|
{
|
||||||
|
get => CUIProps.OutlineColor.Value;
|
||||||
|
set => CUIProps.OutlineColor.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Will be drawn in background with BackgroundColor
|
||||||
|
/// Default is solid white 1x1 texture
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUISprite BackgroundSprite
|
||||||
|
{
|
||||||
|
get => CUIProps.BackgroundSprite.Value;
|
||||||
|
set => CUIProps.BackgroundSprite.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// If true, mouse events on transparent pixels will be ignored
|
||||||
|
/// Note: this will buffer texture data and potentially consume a lot of memory
|
||||||
|
/// so use wisely
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public bool IgnoreTransparent
|
||||||
|
{
|
||||||
|
get => CUIProps.IgnoreTransparent.Value;
|
||||||
|
set => CUIProps.IgnoreTransparent.SetValue(value);
|
||||||
|
}
|
||||||
|
//TODO i think those colors could be stored inside sprites
|
||||||
|
// But then it'll be much harder to apply side effects, think about it
|
||||||
|
/// <summary>
|
||||||
|
/// Color of BackgroundSprite, default is black
|
||||||
|
/// If you're using custom sprite and don't see it make sure this color is not black
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public Color BackgroundColor
|
||||||
|
{
|
||||||
|
get => CUIProps.BackgroundColor.Value;
|
||||||
|
set => CUIProps.BackgroundColor.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float transparency = 1.0f;
|
||||||
|
public float Transparency
|
||||||
|
{
|
||||||
|
get => transparency;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
transparency = value;
|
||||||
|
foreach (CUIComponent child in Children)
|
||||||
|
{
|
||||||
|
if (!child.IgnoreParentTransparency) child.Transparency = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This palette will be used to resolve palette styles
|
||||||
|
/// Primary, Secondary, Tertiary, Quaternary
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public PaletteOrder Palette
|
||||||
|
{
|
||||||
|
get => CUIProps.Palette.Value;
|
||||||
|
set => CUIProps.Palette.SetValue(value);
|
||||||
|
}
|
||||||
|
public PaletteOrder DeepPalette
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Palette = value;
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
child.DeepPalette = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Had to expose resize handle props, because it's not a real component
|
||||||
|
/// and can't really use styles
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public Color ResizeHandleColor { get; set; } = Color.White;
|
||||||
|
[CUISerializable]
|
||||||
|
public Color ResizeHandleGrabbedColor { get; set; } = Color.Cyan;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// don't
|
||||||
|
/// </summary>
|
||||||
|
public SamplerState SamplerState { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Should children be cut off by scissor rect, this is just visual, it's not the same as culling
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool HideChildrenOutsideFrame { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// if child rect doesn't intersect with parent it won't be drawn and won't consume fps
|
||||||
|
/// It also sets HideChildrenOutsideFrame
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public bool CullChildren
|
||||||
|
{
|
||||||
|
get => CUIProps.CullChildren.Value;
|
||||||
|
set => CUIProps.CullChildren.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// It shouldn't be culled off even outside of parent bounds and even if parent demands so
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool UnCullable { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Will shift all children by this much, e.g. this is how scroll works
|
||||||
|
/// It's also 3D
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUI3DOffset ChildrenOffset
|
||||||
|
{
|
||||||
|
get => CUIProps.ChildrenOffset.Value;
|
||||||
|
set => CUIProps.ChildrenOffset.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limits to children positions
|
||||||
|
/// </summary>
|
||||||
|
public Func<CUIRect, CUIBoundaries> ChildrenBoundaries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should it ignore child offset?
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool Fixed { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// this point of this component
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public Vector2 Anchor { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// will be attached to this point of parent
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public Vector2? ParentAnchor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ghost components don't affect layout
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUIBool2 Ghost
|
||||||
|
{
|
||||||
|
get => CUIProps.Ghost.Value;
|
||||||
|
set => CUIProps.Ghost.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Components are drawn in order of their ZIndex
|
||||||
|
/// Normally it's derived from component position in the tree,
|
||||||
|
/// but this will override it
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public int? ZIndex
|
||||||
|
{
|
||||||
|
get => CUIProps.ZIndex.Value;
|
||||||
|
set => CUIProps.ZIndex.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true component will set it's Absolute size to sprite texture size
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public bool ResizeToSprite
|
||||||
|
{
|
||||||
|
get => CUIProps.ResizeToSprite.Value;
|
||||||
|
set => CUIProps.ResizeToSprite.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will be resized to fill empty space in list components
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUIBool2 FillEmptySpace
|
||||||
|
{
|
||||||
|
get => CUIProps.FillEmptySpace.Value;
|
||||||
|
set => CUIProps.FillEmptySpace.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Will resize itself to fit components with absolute size, e.g. text
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUIBool2 FitContent
|
||||||
|
{
|
||||||
|
get => CUIProps.FitContent.Value;
|
||||||
|
set => CUIProps.FitContent.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Absolute size and position in pixels
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUINullRect Absolute
|
||||||
|
{
|
||||||
|
get => CUIProps.Absolute.Value;
|
||||||
|
set => CUIProps.Absolute.SetValue(value);
|
||||||
|
}
|
||||||
|
[CUISerializable]
|
||||||
|
public CUINullRect AbsoluteMin
|
||||||
|
{
|
||||||
|
get => CUIProps.AbsoluteMin.Value;
|
||||||
|
set => CUIProps.AbsoluteMin.SetValue(value);
|
||||||
|
}
|
||||||
|
[CUISerializable]
|
||||||
|
public CUINullRect AbsoluteMax
|
||||||
|
{
|
||||||
|
get => CUIProps.AbsoluteMax.Value;
|
||||||
|
set => CUIProps.AbsoluteMax.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Relative to parent size and position, [0..1]
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUINullRect Relative
|
||||||
|
{
|
||||||
|
get => CUIProps.Relative.Value;
|
||||||
|
set => CUIProps.Relative.SetValue(value);
|
||||||
|
}
|
||||||
|
[CUISerializable]
|
||||||
|
public CUINullRect RelativeMin
|
||||||
|
{
|
||||||
|
get => CUIProps.RelativeMin.Value;
|
||||||
|
set => CUIProps.RelativeMin.SetValue(value);
|
||||||
|
}
|
||||||
|
[CUISerializable]
|
||||||
|
public CUINullRect RelativeMax
|
||||||
|
{
|
||||||
|
get => CUIProps.RelativeMax.Value;
|
||||||
|
set => CUIProps.RelativeMax.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// It's like Relative, but to the opposite dimension
|
||||||
|
/// E.g. Real.Width = CrossRelative.Width * Parent.Real.Height
|
||||||
|
/// Handy for creating square things
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUINullRect CrossRelative
|
||||||
|
{
|
||||||
|
get => CUIProps.CrossRelative.Value;
|
||||||
|
set => CUIProps.CrossRelative.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used in Grid, space separated Row sizes, either in pixels (123) or in % (123%)
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string GridTemplateRows { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Used in Grid, space separated Columns sizes, either in pixels (123) or in % (123%)
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string GridTemplateColumns { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Component will be placed in this cell in the grid component
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public Point? GridStartCell { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// And resized to fit cells from GridStartCell to GridEndCell
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public Point? GridEndCell { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Sets both GridStartCell and GridEndCell at once
|
||||||
|
/// </summary>
|
||||||
|
public Point? GridCell
|
||||||
|
{
|
||||||
|
get => GridStartCell;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
GridStartCell = value;
|
||||||
|
GridEndCell = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
#region Layout --------------------------------------------------------
|
||||||
|
|
||||||
|
protected CUILayout layout;
|
||||||
|
//[CUISerializable]
|
||||||
|
public virtual CUILayout Layout
|
||||||
|
{
|
||||||
|
get => layout;
|
||||||
|
set { layout = value; layout.Host = this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action OnLayoutUpdated;
|
||||||
|
public void InvokeOnLayoutUpdated() => OnLayoutUpdated?.Invoke();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers recalculation of layouts from parent and below
|
||||||
|
/// </summary>
|
||||||
|
internal void OnPropChanged([CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
Layout.Changed = true;
|
||||||
|
CUIDebug.Capture(null, this, "OnPropChanged", memberName, "Layout.Changed", "true");
|
||||||
|
MainComponent?.LayoutChanged();
|
||||||
|
}
|
||||||
|
internal void OnSelfAndParentChanged([CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
Layout.SelfAndParentChanged = true;
|
||||||
|
CUIDebug.Capture(null, this, "OnSelfAndParentChanged", memberName, "Layout.SelfAndParentChanged", "true");
|
||||||
|
MainComponent?.LayoutChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers recalc of own pseudo components and nothing else
|
||||||
|
/// </summary>
|
||||||
|
internal void OnDecorPropChanged([CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
Layout.DecorChanged = true;
|
||||||
|
CUIDebug.Capture(null, this, "OnDecorPropChanged", memberName, "Layout.DecorChanged", "true");
|
||||||
|
MainComponent?.LayoutChanged();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Notifies parent (only) than it may need to ResizeToContent
|
||||||
|
/// </summary>
|
||||||
|
internal void OnAbsolutePropChanged([CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
Layout.AbsoluteChanged = true;
|
||||||
|
CUIDebug.Capture(null, this, "OnAbsolutePropChanged", memberName, "Layout.AbsoluteChanged", "true");
|
||||||
|
MainComponent?.LayoutChanged();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers recalculation of layouts from this and below
|
||||||
|
/// </summary>
|
||||||
|
internal void OnChildrenPropChanged([CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
Layout.ChildChanged = true;
|
||||||
|
MainComponent?.LayoutChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
//HACK This is potentially cursed
|
||||||
|
/// <summary>
|
||||||
|
/// Arbitrary data
|
||||||
|
/// </summary>
|
||||||
|
public object Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will prevent serialization to xml if true
|
||||||
|
/// </summary>
|
||||||
|
public bool Unserializable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this a serialization cutoff point
|
||||||
|
/// Parent will serialize children down to this component
|
||||||
|
/// Further serialization should be hadled by this component
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool BreakSerialization { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Some props (like visible) are autopassed to all new childs
|
||||||
|
/// see PassPropsToChild
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool ShouldPassPropsToChildren { get; set; } = true;
|
||||||
|
/// <summary>
|
||||||
|
/// Don't inherit parent Visibility
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool IgnoreParentVisibility { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Don't inherit parent IgnoreEvents
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool IgnoreParentEventIgnorance { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Don't inherit parent ZIndex
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool IgnoreParentZIndex { get; set; }
|
||||||
|
[CUISerializable] public bool IgnoreParentTransparency { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invisible components are not drawn, but still can be interacted with
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public bool Visible
|
||||||
|
{
|
||||||
|
get => CUIProps.Visible.Value;
|
||||||
|
set => CUIProps.Visible.SetValue(value);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Won't react to mouse events
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public bool IgnoreEvents
|
||||||
|
{
|
||||||
|
get => CUIProps.IgnoreEvents.Value;
|
||||||
|
set => CUIProps.IgnoreEvents.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visible + !IgnoreEvents
|
||||||
|
/// </summary>
|
||||||
|
public bool Revealed
|
||||||
|
{
|
||||||
|
get => CUIProps.Revealed.Value;
|
||||||
|
set => CUIProps.Revealed.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//HACK this is meant for buttons, but i want to access it on generic components in CUIMap
|
||||||
|
protected bool disabled;
|
||||||
|
/// <summary>
|
||||||
|
/// Usually means - non interactable, e.g. unclickable gray button
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public virtual bool Disabled
|
||||||
|
{
|
||||||
|
get => disabled;
|
||||||
|
set => disabled = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
public record CompareResult(bool equal, string firstMismatch = "")
|
||||||
|
{
|
||||||
|
public static implicit operator bool(CompareResult r) => r.equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DeepCompareVerbose(CUIComponent a, CUIComponent b)
|
||||||
|
{
|
||||||
|
CompareResult result = DeepCompare(a, b);
|
||||||
|
if (result.equal) CUI.Log($"{a} == {b}");
|
||||||
|
else CUI.Log($"{result.firstMismatch}");
|
||||||
|
return result.equal;
|
||||||
|
}
|
||||||
|
public static CompareResult DeepCompare(CUIComponent a, CUIComponent b)
|
||||||
|
{
|
||||||
|
if (a.GetType() != b.GetType()) return new CompareResult(false, $"type mismatch: {a} | {b}");
|
||||||
|
|
||||||
|
Type T = a.GetType();
|
||||||
|
CUITypeMetaData meta = CUITypeMetaData.Get(T);
|
||||||
|
|
||||||
|
foreach (var (key, pi) in meta.Serializable)
|
||||||
|
{
|
||||||
|
if (!object.Equals(pi.GetValue(a), pi.GetValue(b)))
|
||||||
|
{
|
||||||
|
return new CompareResult(false, $"{pi}: {a}{pi.GetValue(a)} | {b}{pi.GetValue(b)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Children.Count != b.Children.Count)
|
||||||
|
{
|
||||||
|
return new CompareResult(false, $"child count mismatch: {a}{CUI.ArrayToString(a.Children)} | {b}{CUI.ArrayToString(b.Children)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < a.Children.Count; i++)
|
||||||
|
{
|
||||||
|
CompareResult sub = DeepCompare(a.Children[i], b.Children[i]);
|
||||||
|
if (!sub.equal) return sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CompareResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region State --------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// State is just a clone component with copies of all props
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, CUIComponent> States { get; set; } = new();
|
||||||
|
// TODO why all clones are unreal? this is sneaky, and i don't remember what's it for
|
||||||
|
public CUIComponent Clone()
|
||||||
|
{
|
||||||
|
CUIComponent clone = new CUIComponent()
|
||||||
|
{
|
||||||
|
Unreal = true,
|
||||||
|
};
|
||||||
|
clone.ApplyState(this);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveStateAs(string name) => States[name] = this.Clone();
|
||||||
|
public void LoadState(string name) => ApplyState(States.GetValueOrDefault(name));
|
||||||
|
public void ForgetState(string name) => States.Remove(name);
|
||||||
|
|
||||||
|
//TODO think about edge cases (PassPropsToChild)
|
||||||
|
public void ApplyState(CUIComponent state)
|
||||||
|
{
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
if (state == null) return;
|
||||||
|
|
||||||
|
//TODO why not closest relative?
|
||||||
|
Type targetType = state.GetType() == GetType() ? GetType() : typeof(CUIComponent);
|
||||||
|
|
||||||
|
CUITypeMetaData meta = CUITypeMetaData.Get(targetType);
|
||||||
|
|
||||||
|
//TODO Megacringe, fix it
|
||||||
|
foreach (PropertyInfo pi in meta.Serializable.Values)
|
||||||
|
{
|
||||||
|
if (pi.PropertyType.IsValueType || pi.PropertyType == typeof(string))
|
||||||
|
{
|
||||||
|
pi.SetValue(this, pi.GetValue(state));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
object value = pi.GetValue(state);
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
pi.SetValue(this, null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pi.PropertyType.IsAssignableTo(typeof(ICloneable)))
|
||||||
|
{
|
||||||
|
ICloneable cloneable = (ICloneable)pi.GetValue(state);
|
||||||
|
object clone = cloneable.Clone();
|
||||||
|
pi.SetValue(this, clone);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CUI.Info($"Ekhem, can't copy {pi} prop from {state} to {this} because it's not cloneable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO Megacringe, fix it
|
||||||
|
foreach (PropertyInfo pi in meta.Serializable.Values)
|
||||||
|
{
|
||||||
|
if (pi.PropertyType.IsValueType && !object.Equals(pi.GetValue(state), pi.GetValue(this)))
|
||||||
|
{
|
||||||
|
pi.SetValue(this, pi.GetValue(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region XML --------------------------------------------------------
|
||||||
|
|
||||||
|
public static bool ForceSaveAllProps { get; set; } = false;
|
||||||
|
public static bool SaveAfterLoad { get; set; } = true;
|
||||||
|
|
||||||
|
public string SavePath { get; set; }
|
||||||
|
|
||||||
|
public virtual XElement ToXML(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Unserializable) return null;
|
||||||
|
|
||||||
|
Type type = GetType();
|
||||||
|
|
||||||
|
XElement e = new XElement(type.Name);
|
||||||
|
|
||||||
|
PackProps(e, propAttribute);
|
||||||
|
|
||||||
|
foreach (CUIComponent child in Children)
|
||||||
|
{
|
||||||
|
if (!this.BreakSerialization)
|
||||||
|
{
|
||||||
|
e.Add(child.ToXML(propAttribute));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning(e);
|
||||||
|
return new XElement("Error", e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual void FromXML(XElement element, string baseFolder = null)
|
||||||
|
{
|
||||||
|
foreach (XElement childElement in element.Elements())
|
||||||
|
{
|
||||||
|
Type childType = CUIReflection.GetComponentTypeByName(childElement.Name.ToString());
|
||||||
|
if (childType == null) continue;
|
||||||
|
|
||||||
|
CUIComponent child = (CUIComponent)Activator.CreateInstance(childType);
|
||||||
|
child.FromXML(childElement, baseFolder);
|
||||||
|
|
||||||
|
//CUI.Log($"{this}[{child.AKA}] = {child} ");
|
||||||
|
this.Append(child, child.AKA);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractProps(element, baseFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ExtractProps(XElement element, string baseFolder = null)
|
||||||
|
{
|
||||||
|
Type type = GetType();
|
||||||
|
|
||||||
|
CUITypeMetaData meta = CUITypeMetaData.Get(type);
|
||||||
|
|
||||||
|
foreach (XAttribute attribute in element.Attributes())
|
||||||
|
{
|
||||||
|
if (!meta.Serializable.ContainsKey(attribute.Name.ToString()))
|
||||||
|
{
|
||||||
|
CUIDebug.Error($"Can't parse prop {attribute.Name} in {type.Name} because type metadata doesn't contain that prop (is it a property? fields aren't supported yet)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyInfo prop = meta.Serializable[attribute.Name.ToString()];
|
||||||
|
|
||||||
|
MethodInfo parse = null;
|
||||||
|
if (CUIExtensions.Parse.ContainsKey(prop.PropertyType))
|
||||||
|
{
|
||||||
|
parse = CUIExtensions.Parse[prop.PropertyType];
|
||||||
|
}
|
||||||
|
|
||||||
|
parse ??= prop.PropertyType.GetMethod(
|
||||||
|
"Parse",
|
||||||
|
BindingFlags.Public | BindingFlags.Static,
|
||||||
|
new Type[] { typeof(string) }
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Func<string, object> ParseWithContext = null;
|
||||||
|
//HACK
|
||||||
|
if (prop.PropertyType == typeof(CUISprite) && baseFolder != null)
|
||||||
|
{
|
||||||
|
ParseWithContext = (raw) => CUISprite.ParseWithContext(raw, baseFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (parse == null)
|
||||||
|
{
|
||||||
|
if (prop.PropertyType.IsEnum)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
prop.SetValue(this, Enum.Parse(prop.PropertyType, attribute.Value));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUIDebug.Error($"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CUIDebug.Error($"Can't parse prop {prop.Name} in {type.Name} because it's type {prop.PropertyType.Name} is missing Parse method");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object result = null;
|
||||||
|
if (ParseWithContext != null)
|
||||||
|
{
|
||||||
|
result = ParseWithContext(attribute.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = parse.Invoke(null, new object[] { attribute.Value });
|
||||||
|
}
|
||||||
|
prop.SetValue(this, result);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUIDebug.Error($"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void PackProps(XElement element, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||||
|
{
|
||||||
|
Type type = GetType();
|
||||||
|
CUITypeMetaData meta = CUITypeMetaData.Get(type);
|
||||||
|
|
||||||
|
SortedDictionary<string, PropertyInfo> props = propAttribute switch
|
||||||
|
{
|
||||||
|
CUIAttribute.CUISerializable => meta.Serializable,
|
||||||
|
CUIAttribute.Calculated => meta.Calculated,
|
||||||
|
_ => meta.Serializable,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string key in props.Keys)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object value = props[key].GetValue(this);
|
||||||
|
// it's default value for this prop
|
||||||
|
if (!ForceSaveAllProps && meta.Default != null && Object.Equals(value, CUIReflection.GetNestedValue(meta.Default, key)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodInfo customToString = CUIExtensions.CustomToString.GetValueOrDefault(props[key].PropertyType);
|
||||||
|
|
||||||
|
if (customToString != null)
|
||||||
|
{
|
||||||
|
element?.SetAttributeValue(key, customToString.Invoke(null, new object[] { value }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element?.SetAttributeValue(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Failed to serialize prop: {e.Message}");
|
||||||
|
CUI.Warning($"{key} in {this}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string Serialize(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XElement e = this.ToXML(propAttribute);
|
||||||
|
return e.ToString();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Error(e);
|
||||||
|
return e.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static CUIComponent Deserialize(string raw, string baseFolder = null)
|
||||||
|
{
|
||||||
|
return Deserialize(XElement.Parse(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CUIComponent Deserialize(XElement e, string baseFolder = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Type type = CUIReflection.GetComponentTypeByName(e.Name.ToString());
|
||||||
|
if (type == null) return null;
|
||||||
|
|
||||||
|
CUIComponent c = (CUIComponent)Activator.CreateInstance(type);
|
||||||
|
// c.RemoveAllChildren();
|
||||||
|
c.FromXML(e, baseFolder);
|
||||||
|
CUIComponent.RunRecursiveOn(c, (component) => component.Hydrate());
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CUIDebug.Error(ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadSelfFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XDocument xdoc = XDocument.Load(path);
|
||||||
|
|
||||||
|
RemoveAllChildren();
|
||||||
|
if (searchForSpritesInTheSameFolder) FromXML(xdoc.Root, Path.GetDirectoryName(path));
|
||||||
|
else FromXML(xdoc.Root);
|
||||||
|
|
||||||
|
CUIComponent.RunRecursiveOn(this, (component) => component.Hydrate());
|
||||||
|
SavePath = path;
|
||||||
|
|
||||||
|
if (SaveAfterLoad && saveAfterLoad) SaveToTheSamePath();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CUI.Warning(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CUIComponent LoadFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XDocument xdoc = XDocument.Load(path);
|
||||||
|
CUIComponent result;
|
||||||
|
if (searchForSpritesInTheSameFolder)
|
||||||
|
{
|
||||||
|
result = Deserialize(xdoc.Root, Path.GetDirectoryName(path));
|
||||||
|
}
|
||||||
|
else result = Deserialize(xdoc.Root);
|
||||||
|
|
||||||
|
result.SavePath = path;
|
||||||
|
|
||||||
|
if (SaveAfterLoad && saveAfterLoad) result.SaveToTheSamePath();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CUIDebug.Error(ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T LoadFromFile<T>(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false) where T : CUIComponent
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XDocument xdoc = XDocument.Load(path);
|
||||||
|
T result;
|
||||||
|
if (searchForSpritesInTheSameFolder)
|
||||||
|
{
|
||||||
|
result = (T)Deserialize(xdoc.Root, Path.GetDirectoryName(path));
|
||||||
|
}
|
||||||
|
else result = (T)Deserialize(xdoc.Root);
|
||||||
|
|
||||||
|
result.SavePath = path;
|
||||||
|
|
||||||
|
if (SaveAfterLoad && saveAfterLoad) result.SaveToTheSamePath();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CUIDebug.Error(ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadFromTheSameFile()
|
||||||
|
{
|
||||||
|
if (SavePath == null)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Can't load {this} from The Same Path, SavePath is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LoadSelfFromFile(SavePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveToTheSamePath()
|
||||||
|
{
|
||||||
|
if (SavePath == null)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Can't save {this} To The Same Path, SavePath is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SaveToFile(SavePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveToFile(string path, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XDocument xdoc = new XDocument();
|
||||||
|
xdoc.Add(this.ToXML(propAttribute));
|
||||||
|
xdoc.Save(path);
|
||||||
|
SavePath = path;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Experimental method
|
||||||
|
/// Here you can add data/ callbacks/ save stuff to variables
|
||||||
|
/// after loading a xml skeletom
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Hydrate()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent : IDisposable
|
||||||
|
{
|
||||||
|
private void SetupStyles()
|
||||||
|
{
|
||||||
|
Style = new CUIStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use it to e.g. update component color
|
||||||
|
/// </summary>
|
||||||
|
public event Action OnStyleApplied;
|
||||||
|
internal void InvokeOnStyleApplied() => OnStyleApplied?.Invoke();
|
||||||
|
|
||||||
|
private void HandleStylePropChange(string key, string value)
|
||||||
|
{
|
||||||
|
CUIGlobalStyleResolver.OnComponentStylePropChanged(this, key);
|
||||||
|
}
|
||||||
|
private void HandleStyleChange(CUIStyle s)
|
||||||
|
{
|
||||||
|
CUIGlobalStyleResolver.OnComponentStyleChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CUIStyle style;
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to assing parsable string or link to CUIPalette to any prop
|
||||||
|
/// It's indexable, so you can access it like this: component.Style["BackgroundColor"] = "cyan"
|
||||||
|
/// if value starts with "CUIPalette." it will extract the value from palette
|
||||||
|
/// e.g. component.Style["BackgroundColor"] = "CUIPalette.DarkBlue.Secondary.On"
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public CUIStyle Style
|
||||||
|
{
|
||||||
|
get => style;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (style == value) return;
|
||||||
|
|
||||||
|
if (style != null)
|
||||||
|
{
|
||||||
|
style.OnUse -= HandleStyleChange;
|
||||||
|
style.OnPropChanged -= HandleStylePropChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
style = value;
|
||||||
|
|
||||||
|
if (style != null)
|
||||||
|
{
|
||||||
|
style.OnUse += HandleStyleChange;
|
||||||
|
style.OnPropChanged += HandleStylePropChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleStyleChange(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIStyle ResolvedStyle { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUIComponent
|
||||||
|
{
|
||||||
|
#region Tree --------------------------------------------------------
|
||||||
|
|
||||||
|
public List<CUIComponent> Children { get; set; } = new();
|
||||||
|
|
||||||
|
private CUIComponent? parent; public CUIComponent? Parent
|
||||||
|
{
|
||||||
|
get => parent;
|
||||||
|
set => SetParent(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetParent(CUIComponent? value, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
TreeChanged = true;
|
||||||
|
OnPropChanged();
|
||||||
|
parent.Forget(this);
|
||||||
|
parent.Children.Remove(this);
|
||||||
|
parent.OnChildRemoved?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = value;
|
||||||
|
|
||||||
|
CUIDebug.Capture(null, this, "SetParent", memberName, "parent", $"{parent}");
|
||||||
|
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
if (parent is CUIMainComponent main) MainComponent = main;
|
||||||
|
if (parent?.MainComponent != null) MainComponent = parent.MainComponent;
|
||||||
|
|
||||||
|
//parent.Children.Add(this);
|
||||||
|
TreeChanged = true;
|
||||||
|
if (AKA != null) parent.Remember(this, AKA);
|
||||||
|
parent.PassPropsToChild(this);
|
||||||
|
OnPropChanged();
|
||||||
|
parent.OnChildAdded?.Invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool treeChanged = true; internal bool TreeChanged
|
||||||
|
{
|
||||||
|
get => treeChanged;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
treeChanged = value;
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
OnTreeChanged?.Invoke();
|
||||||
|
if (Parent != null) Parent.TreeChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to add array of children
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<CUIComponent> AddChildren
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (CUIComponent c in value) { Append(c); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<CUIComponent> OnChildAdded;
|
||||||
|
public event Action<CUIComponent> OnChildRemoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds children to the end of the list
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="child"></param>
|
||||||
|
/// <param name="name"> AKA </param>
|
||||||
|
/// <returns> child </returns>
|
||||||
|
public virtual CUIComponent Append(CUIComponent child, string name = null, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (child == null) return child;
|
||||||
|
|
||||||
|
child.Parent = this;
|
||||||
|
Children.Add(child);
|
||||||
|
if (name != null) Remember(child, name);
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds children to the begining of the list
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="child"></param>
|
||||||
|
/// <param name="name"> AKA </param>
|
||||||
|
/// <returns> child </returns>
|
||||||
|
public virtual CUIComponent Prepend(CUIComponent child, string name = null, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (child == null) return child;
|
||||||
|
|
||||||
|
child.Parent = this;
|
||||||
|
Children.Insert(0, child);
|
||||||
|
if (name != null) Remember(child, name);
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual CUIComponent Insert(CUIComponent child, int index, string name = null, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (child == null) return child;
|
||||||
|
|
||||||
|
child.Parent = this;
|
||||||
|
index = Math.Clamp(index, 0, Children.Count);
|
||||||
|
Children.Insert(index, child);
|
||||||
|
if (name != null) Remember(child, name);
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO DRY
|
||||||
|
public void RemoveSelf() => Parent?.RemoveChild(this);
|
||||||
|
public CUIComponent RemoveChild(CUIComponent child, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (child == null || !Children.Contains(child)) return child;
|
||||||
|
|
||||||
|
if (this != null) // kek
|
||||||
|
{
|
||||||
|
child.TreeChanged = true;
|
||||||
|
child.OnPropChanged();
|
||||||
|
//HACK i'm sure it doesn't belong here, find a better place
|
||||||
|
forsedSize = new CUINullVector2();
|
||||||
|
OnAbsolutePropChanged();
|
||||||
|
// Forget(child);
|
||||||
|
Children.Remove(child);
|
||||||
|
OnChildRemoved?.Invoke(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
child.parent = null;
|
||||||
|
|
||||||
|
CUIDebug.Capture(null, this, "RemoveChild", memberName, "child", $"{child}");
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO DRY
|
||||||
|
public void RemoveAllChildren([CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
foreach (CUIComponent c in Children)
|
||||||
|
{
|
||||||
|
if (this != null) // kek
|
||||||
|
{
|
||||||
|
c.TreeChanged = true;
|
||||||
|
c.OnPropChanged();
|
||||||
|
//Forget(c);
|
||||||
|
//Children.Remove(c);
|
||||||
|
OnChildRemoved?.Invoke(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.parent = null;
|
||||||
|
|
||||||
|
CUIDebug.Capture(null, this, "RemoveAllChildren", memberName, "child", $"{c}");
|
||||||
|
}
|
||||||
|
|
||||||
|
NamedComponents.Clear();
|
||||||
|
Children.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pass props like ZIndex, Visible to a new child
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="child"></param>
|
||||||
|
protected virtual void PassPropsToChild(CUIComponent child)
|
||||||
|
{
|
||||||
|
if (!ShouldPassPropsToChildren) return;
|
||||||
|
|
||||||
|
//child.Palette = Palette;
|
||||||
|
if (ZIndex.HasValue && !child.IgnoreParentZIndex) child.ZIndex = ZIndex.Value + 1;
|
||||||
|
if (IgnoreEvents && !child.IgnoreParentEventIgnorance) child.IgnoreEvents = true;
|
||||||
|
if (!Visible && !child.IgnoreParentVisibility) child.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for all components
|
||||||
|
/// </summary>
|
||||||
|
public partial class CUIComponent : IDisposable
|
||||||
|
{
|
||||||
|
#region Static --------------------------------------------------------
|
||||||
|
internal static void InitStatic()
|
||||||
|
{
|
||||||
|
CUI.OnInit += () =>
|
||||||
|
{
|
||||||
|
MaxID = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
CUI.OnDispose += () =>
|
||||||
|
{
|
||||||
|
foreach (int id in ComponentsById.Keys)
|
||||||
|
{
|
||||||
|
CUIComponent component = null;
|
||||||
|
ComponentsById[id].TryGetTarget(out component);
|
||||||
|
component?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentsById.Clear();
|
||||||
|
ComponentsByType.Clear();
|
||||||
|
|
||||||
|
|
||||||
|
dummyComponent = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
internal static int MaxID;
|
||||||
|
public static Dictionary<int, WeakReference<CUIComponent>> ComponentsById = new();
|
||||||
|
public static WeakCatalog<Type, CUIComponent> ComponentsByType = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used to trick vanilla GUI into believing that
|
||||||
|
/// mouse is hovering some component and block clicks
|
||||||
|
/// </summary>
|
||||||
|
public static GUIButton dummyComponent = new GUIButton(new RectTransform(new Point(0, 0)))
|
||||||
|
{
|
||||||
|
Text = "DUMMY",
|
||||||
|
};
|
||||||
|
/// <summary>
|
||||||
|
/// designed to be versatile, in fact never used
|
||||||
|
/// </summary>
|
||||||
|
public static void RunRecursiveOn(CUIComponent component, Action<CUIComponent> action)
|
||||||
|
{
|
||||||
|
action(component);
|
||||||
|
foreach (CUIComponent child in component.Children)
|
||||||
|
{
|
||||||
|
RunRecursiveOn(child, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ForEach(Action<CUIComponent> action)
|
||||||
|
{
|
||||||
|
foreach (int id in ComponentsById.Keys)
|
||||||
|
{
|
||||||
|
CUIComponent component = null;
|
||||||
|
ComponentsById[id].TryGetTarget(out component);
|
||||||
|
if (component is not null) action(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Type> GetClassHierarchy(Type type)
|
||||||
|
{
|
||||||
|
while (type != typeof(Object) && type != null)
|
||||||
|
{
|
||||||
|
yield return type;
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Type> GetReverseClassHierarchy(Type type)
|
||||||
|
=> CUIComponent.GetClassHierarchy(type).Reverse<Type>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Virtual --------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
//TODO move to cui props, it's a bit more clampicated than ChildrenBoundaries
|
||||||
|
/// <summary>
|
||||||
|
/// Bounds for offset, e.g. scroll, zoom
|
||||||
|
/// </summary>
|
||||||
|
internal virtual CUIBoundaries ChildOffsetBounds => new CUIBoundaries();
|
||||||
|
/// <summary>
|
||||||
|
/// "Component like" ghost stuff that can't have children and
|
||||||
|
/// doesn't impact layout. Drag handles, text etc
|
||||||
|
/// </summary>
|
||||||
|
internal virtual void UpdatePseudoChildren()
|
||||||
|
{
|
||||||
|
LeftResizeHandle.Update();
|
||||||
|
RightResizeHandle.Update();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Last chance to disagree with proposed size
|
||||||
|
/// For stuff that should resize to content
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size"> proposed size </param>
|
||||||
|
/// <returns> size you're ok with </returns>
|
||||||
|
internal virtual Vector2 AmIOkWithThisSize(Vector2 size) => size;
|
||||||
|
/// <summary>
|
||||||
|
/// Here component should be drawn
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spriteBatch"></param>
|
||||||
|
public virtual partial void Draw(SpriteBatch spriteBatch);
|
||||||
|
/// <summary>
|
||||||
|
/// Method for drawing something that should always be on top, e.g. resize handles
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spriteBatch"></param>
|
||||||
|
public virtual partial void DrawFront(SpriteBatch spriteBatch);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Draw --------------------------------------------------------
|
||||||
|
|
||||||
|
public virtual partial void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (BackgroundVisible) CUI.DrawRectangle(spriteBatch, Real, BackgroundColor * Transparency, BackgroundSprite);
|
||||||
|
|
||||||
|
CUI.DrawBorders(spriteBatch, this);
|
||||||
|
// if (Border.Visible) GUI.DrawRectangle(spriteBatch, BorderBox.Position, BorderBox.Size, Border.Color, thickness: Border.Thickness);
|
||||||
|
|
||||||
|
if (OutlineVisible) GUI.DrawRectangle(spriteBatch, OutlineBox.Position, OutlineBox.Size, OutlineColor, thickness: OutlineThickness);
|
||||||
|
|
||||||
|
LeftResizeHandle.Draw(spriteBatch);
|
||||||
|
RightResizeHandle.Draw(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual partial void DrawFront(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (DebugHighlight)
|
||||||
|
{
|
||||||
|
GUI.DrawRectangle(spriteBatch, Real.Position, Real.Size, Color.Cyan * 0.5f, isFilled: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Constructors --------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
internal void Vitalize()
|
||||||
|
{
|
||||||
|
foreach (FieldInfo fi in this.GetType().GetFields(AccessTools.all))
|
||||||
|
{
|
||||||
|
if (fi.FieldType.IsAssignableTo(typeof(ICUIVitalizable)))
|
||||||
|
{
|
||||||
|
ICUIVitalizable prop = (ICUIVitalizable)fi.GetValue(this);
|
||||||
|
if (prop == null) continue;
|
||||||
|
prop.SetHost(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal void VitalizeProps()
|
||||||
|
{
|
||||||
|
foreach (FieldInfo fi in this.GetType().GetFields(AccessTools.all))
|
||||||
|
{
|
||||||
|
if (fi.FieldType.IsAssignableTo(typeof(ICUIProp)))
|
||||||
|
{
|
||||||
|
ICUIProp prop = (ICUIProp)fi.GetValue(this);
|
||||||
|
if (prop == null) continue; // this is for Main.GrabbedDragHandle
|
||||||
|
prop.SetHost(this);
|
||||||
|
prop.SetName(fi.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (FieldInfo fi in typeof(CUIComponentProps).GetFields(AccessTools.all))
|
||||||
|
{
|
||||||
|
if (fi.FieldType.IsAssignableTo(typeof(ICUIProp)))
|
||||||
|
{
|
||||||
|
ICUIProp prop = (ICUIProp)fi.GetValue(CUIProps);
|
||||||
|
if (prop == null) continue;
|
||||||
|
prop.SetHost(this);
|
||||||
|
prop.SetName(fi.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIComponent()
|
||||||
|
{
|
||||||
|
if (CUI.Disposed)
|
||||||
|
{
|
||||||
|
Disposed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID = MaxID++;
|
||||||
|
|
||||||
|
ComponentsById[ID] = new WeakReference<CUIComponent>(this);
|
||||||
|
ComponentsByType.Add(this.GetType(), this);
|
||||||
|
|
||||||
|
Vitalize();
|
||||||
|
VitalizeProps();
|
||||||
|
|
||||||
|
SetupCommands();
|
||||||
|
|
||||||
|
Layout = new CUILayoutSimple();
|
||||||
|
|
||||||
|
SetupStyles();
|
||||||
|
SetupAnimations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIComponent(float? x = null, float? y = null, float? w = null, float? h = null) : this()
|
||||||
|
{
|
||||||
|
Relative = new CUINullRect(x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Disposed;
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Disposed) return;
|
||||||
|
CleanUp();
|
||||||
|
Disposed = true;
|
||||||
|
}
|
||||||
|
public virtual void CleanUp() { }
|
||||||
|
|
||||||
|
~CUIComponent() => Dispose();
|
||||||
|
|
||||||
|
public override string ToString() => $"{this.GetType().Name}:{ID}:{AKA}";
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Drop down list, aka Select
|
||||||
|
/// </summary>
|
||||||
|
public class CUIDropDown : CUIComponent
|
||||||
|
{
|
||||||
|
internal class DDOption : CUIButton
|
||||||
|
{
|
||||||
|
public DDOption() : this("") { }
|
||||||
|
public DDOption(string text) : base(text) { }
|
||||||
|
}
|
||||||
|
private CUIButton MainButton;
|
||||||
|
private CUIVerticalList OptionBox;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of options
|
||||||
|
/// Options are just strings
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public IEnumerable<string> Options
|
||||||
|
{
|
||||||
|
get => OptionBox.Children.Cast<DDOption>().Select(o => o.Text);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
foreach (string option in value) { Add(option); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[CUISerializable]
|
||||||
|
public string Selected
|
||||||
|
{
|
||||||
|
get => MainButton.Text;
|
||||||
|
set => Select(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<string> OnSelect;
|
||||||
|
public Action<string> AddOnSelect { set { OnSelect += value; } }
|
||||||
|
|
||||||
|
|
||||||
|
public void Open() => OptionBox.Revealed = true;
|
||||||
|
public void Close() => OptionBox.Revealed = false;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
OptionBox.RemoveAllChildren();
|
||||||
|
Select("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string option)
|
||||||
|
{
|
||||||
|
OptionBox.Append(new DDOption(option)
|
||||||
|
{
|
||||||
|
AddOnMouseDown = (e) => Select(option),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Select(int i) => Select(Options.ElementAtOrDefault(i));
|
||||||
|
public void Select(string option)
|
||||||
|
{
|
||||||
|
MainButton.Text = option ?? "";
|
||||||
|
OptionBox.Revealed = false;
|
||||||
|
OnSelect?.Invoke(MainButton.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(int i) => Remove(Options.ElementAtOrDefault(i));
|
||||||
|
public void Remove(string option)
|
||||||
|
{
|
||||||
|
if (option == null) return;
|
||||||
|
if (!Options.Contains(option)) return;
|
||||||
|
|
||||||
|
DDOption ddoption = OptionBox.Children.Cast<DDOption>().FirstOrDefault(o => o.Text == option);
|
||||||
|
bool wasSelected = MainButton.Text == ddoption.Text;
|
||||||
|
OptionBox.RemoveChild(ddoption);
|
||||||
|
if (wasSelected) Select(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIDropDown() : base()
|
||||||
|
{
|
||||||
|
BreakSerialization = true;
|
||||||
|
OptionBox = new CUIVerticalList()
|
||||||
|
{
|
||||||
|
Relative = new CUINullRect(w: 1),
|
||||||
|
FitContent = new CUIBool2(true, true),
|
||||||
|
Ghost = new CUIBool2(false, true),
|
||||||
|
Anchor = CUIAnchor.TopLeft,
|
||||||
|
ParentAnchor = CUIAnchor.BottomLeft,
|
||||||
|
ZIndex = 500,
|
||||||
|
Style = new CUIStyle(){
|
||||||
|
{"BackgroundColor", "CUIPalette.DDOption.Background"},
|
||||||
|
{"Border", "CUIPalette.DDOption.Border"},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
MainButton = new CUIButton()
|
||||||
|
{
|
||||||
|
Text = "CUIDropDown",
|
||||||
|
Relative = new CUINullRect(w: 1, h: 1),
|
||||||
|
AddOnMouseDown = (e) => OptionBox.Revealed = !OptionBox.Revealed,
|
||||||
|
};
|
||||||
|
|
||||||
|
Append(MainButton);
|
||||||
|
Append(OptionBox);
|
||||||
|
|
||||||
|
FitContent = new CUIBool2(true, true);
|
||||||
|
|
||||||
|
//HACK Why this main is hardcoded?
|
||||||
|
//in static constructor CUI.Main is null and this won't work
|
||||||
|
if (CUI.Main is not null) CUI.Main.OnMouseDown += (e) => Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Draggable and resizable container for other components
|
||||||
|
/// </summary>
|
||||||
|
public class CUIFrame : CUIComponent
|
||||||
|
{
|
||||||
|
public override void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (BackgroundVisible) CUI.DrawRectangle(spriteBatch, Real, BackgroundColor, BackgroundSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawFront(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
//if (BorderVisible) CUI.DrawBorders(spriteBatch, Real, BorderColor, BorderSprite, BorderThickness);
|
||||||
|
// GUI.DrawRectangle(spriteBatch, BorderBox.Position, BorderBox.Size, BorderColor, thickness: BorderThickness);
|
||||||
|
CUI.DrawBorders(spriteBatch, this);
|
||||||
|
|
||||||
|
if (OutlineVisible) GUI.DrawRectangle(spriteBatch, OutlineBox.Position, OutlineBox.Size, OutlineColor, thickness: OutlineThickness);
|
||||||
|
|
||||||
|
LeftResizeHandle.Draw(spriteBatch);
|
||||||
|
RightResizeHandle.Draw(spriteBatch);
|
||||||
|
|
||||||
|
//base.DrawFront(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action OnOpen;
|
||||||
|
public event Action OnClose;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will reveal the frame and append it to CUI.Main
|
||||||
|
/// </summary>
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
if (CUI.Main == null && Parent != CUI.Main) return;
|
||||||
|
CUI.Main.Append(this);
|
||||||
|
Revealed = true;
|
||||||
|
OnOpen?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will hide the frame and remove it from children of CUI.Main
|
||||||
|
/// </summary>
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
RemoveSelf();
|
||||||
|
Revealed = false;
|
||||||
|
OnClose?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIFrame() : base()
|
||||||
|
{
|
||||||
|
CullChildren = true;
|
||||||
|
Resizible = true;
|
||||||
|
Draggable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIFrame(float? x = null, float? y = null, float? w = null, float? h = null) : this()
|
||||||
|
{
|
||||||
|
Relative = new CUINullRect(x, y, w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Grid containing children in its cells
|
||||||
|
/// </summary>
|
||||||
|
public class CUIGrid : CUIComponent
|
||||||
|
{
|
||||||
|
public override CUILayout Layout
|
||||||
|
{
|
||||||
|
get => layout;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
layout = new CUILayoutGrid();
|
||||||
|
layout.Host = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public CUILayoutGrid GridLayout => (CUILayoutGrid)Layout;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public CUIGrid() : base()
|
||||||
|
{
|
||||||
|
//Layout = new CUILayoutGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resizing components to it's Height and placing them sequentially
|
||||||
|
/// </summary>
|
||||||
|
public class CUIHorizontalList : CUIComponent
|
||||||
|
{
|
||||||
|
[CUISerializable] public bool Scrollable { get; set; }
|
||||||
|
[CUISerializable] public float ScrollSpeed { get; set; } = 1.0f;
|
||||||
|
|
||||||
|
public float LeftGap = 0f;
|
||||||
|
public float RightGap = 0f;
|
||||||
|
|
||||||
|
public override CUILayout Layout
|
||||||
|
{
|
||||||
|
get => layout;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
layout = new CUILayoutHorizontalList();
|
||||||
|
layout.Host = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public CUILayoutHorizontalList ListLayout => (CUILayoutHorizontalList)Layout;
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public CUIDirection Direction
|
||||||
|
{
|
||||||
|
get => ListLayout.Direction;
|
||||||
|
set => ListLayout.Direction = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public bool ResizeToHostHeight
|
||||||
|
{
|
||||||
|
get => ListLayout.ResizeToHostHeight;
|
||||||
|
set => ListLayout.ResizeToHostHeight = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Scroll
|
||||||
|
{
|
||||||
|
get => ChildrenOffset.X;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!Scrollable) return;
|
||||||
|
CUIProps.ChildrenOffset.SetValue(
|
||||||
|
ChildrenOffset with { X = value }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override CUIBoundaries ChildOffsetBounds => new CUIBoundaries(
|
||||||
|
minY: 0,
|
||||||
|
maxY: 0,
|
||||||
|
minX: LeftGap,
|
||||||
|
maxX: Math.Min(Real.Width - ListLayout.TotalWidth - RightGap, 0)
|
||||||
|
);
|
||||||
|
public CUIHorizontalList() : base()
|
||||||
|
{
|
||||||
|
CullChildren = true;
|
||||||
|
|
||||||
|
|
||||||
|
OnScroll += (m) => Scroll += m.Scroll * ScrollSpeed;
|
||||||
|
ChildrenBoundaries = CUIBoundaries.HorizontalTube;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
#define SHOWPERF
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Orchestrating drawing and updating of it's children
|
||||||
|
/// Also a CUIComponent, but it's draw and update methods
|
||||||
|
/// Attached directly to games life cycle
|
||||||
|
/// </summary>
|
||||||
|
public class CUIMainComponent : CUIComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper for global events
|
||||||
|
/// </summary>
|
||||||
|
public class CUIGlobalEvents
|
||||||
|
{
|
||||||
|
public Action<CUIInput> OnMouseDown; public void InvokeOnMouseDown(CUIInput e) => OnMouseDown?.Invoke(e);
|
||||||
|
public Action<CUIInput> OnMouseUp; public void InvokeOnMouseUp(CUIInput e) => OnMouseUp?.Invoke(e);
|
||||||
|
public Action<CUIInput> OnMouseMoved; public void InvokeOnMouseMoved(CUIInput e) => OnMouseMoved?.Invoke(e);
|
||||||
|
public Action<CUIInput> OnClick; public void InvokeOnClick(CUIInput e) => OnClick?.Invoke(e);
|
||||||
|
public Action<CUIInput> OnKeyDown; public void InvokeOnKeyDown(CUIInput e) => OnKeyDown?.Invoke(e);
|
||||||
|
public Action<CUIInput> OnKeyUp; public void InvokeOnKeyUp(CUIInput e) => OnKeyUp?.Invoke(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Frozen window doesn't update
|
||||||
|
/// </summary>
|
||||||
|
public bool Frozen { get; set; }
|
||||||
|
public double UpdateInterval = 1.0 / 300.0;
|
||||||
|
/// <summary>
|
||||||
|
/// If true will update layout until it settles to prevent blinking
|
||||||
|
/// </summary>
|
||||||
|
public bool CalculateUntilResolved = true;
|
||||||
|
/// <summary>
|
||||||
|
/// If your GUI needs more than this steps of layout update
|
||||||
|
/// you will get a warning
|
||||||
|
/// </summary>
|
||||||
|
public int MaxLayoutRecalcLoopsPerUpdate = 10;
|
||||||
|
public event Action OnTreeChanged;
|
||||||
|
public Action AddOnTreeChanged { set { OnTreeChanged += value; } }
|
||||||
|
|
||||||
|
public CUIDragHandle GrabbedDragHandle;
|
||||||
|
public CUIResizeHandle GrabbedResizeHandle;
|
||||||
|
public CUISwipeHandle GrabbedSwipeHandle;
|
||||||
|
public CUIComponent MouseOn;
|
||||||
|
public CUIComponent FocusedComponent
|
||||||
|
{
|
||||||
|
get => CUI.FocusedComponent;
|
||||||
|
set => CUI.FocusedComponent = value;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Container for true global events
|
||||||
|
/// CUIMainComponent itself can react to events and you can listen for those,
|
||||||
|
/// but e.g. mouse events may be consumed before they reach Main
|
||||||
|
/// </summary>
|
||||||
|
public CUIGlobalEvents Global = new CUIGlobalEvents();
|
||||||
|
|
||||||
|
private Stopwatch sw = new Stopwatch();
|
||||||
|
|
||||||
|
internal List<CUIComponent> Flat = new List<CUIComponent>();
|
||||||
|
internal List<CUIComponent> Leaves = new List<CUIComponent>();
|
||||||
|
internal SortedList<int, List<CUIComponent>> Layers = new SortedList<int, List<CUIComponent>>();
|
||||||
|
private List<CUIComponent> MouseOnList = new List<CUIComponent>();
|
||||||
|
private Vector2 GrabbedOffset;
|
||||||
|
|
||||||
|
private void RunStraigth(Action<CUIComponent> a) { for (int i = 0; i < Flat.Count; i++) a(Flat[i]); }
|
||||||
|
private void RunReverse(Action<CUIComponent> a) { for (int i = Flat.Count - 1; i >= 0; i--) a(Flat[i]); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void FlattenTree()
|
||||||
|
{
|
||||||
|
int retries = 0;
|
||||||
|
bool done = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
retries++;
|
||||||
|
if (retries > 10) break;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Flat.Clear();
|
||||||
|
Layers.Clear();
|
||||||
|
|
||||||
|
int globalIndex = 0;
|
||||||
|
void CalcZIndexRec(CUIComponent component, int added = 0)
|
||||||
|
{
|
||||||
|
component.positionalZIndex = globalIndex;
|
||||||
|
globalIndex += 1;
|
||||||
|
component.addedZIndex = added;
|
||||||
|
if (component.ZIndex.HasValue) component.addedZIndex += component.ZIndex.Value;
|
||||||
|
|
||||||
|
foreach (CUIComponent child in component.Children)
|
||||||
|
{
|
||||||
|
CalcZIndexRec(child, component.addedZIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CalcZIndexRec(this, 0);
|
||||||
|
RunRecursiveOn(this, (c) =>
|
||||||
|
{
|
||||||
|
int i = c.positionalZIndex + c.addedZIndex;
|
||||||
|
if (!Layers.ContainsKey(i)) Layers[i] = new List<CUIComponent>();
|
||||||
|
Layers[i].Add(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var layer in Layers)
|
||||||
|
{
|
||||||
|
Flat.AddRange(layer.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Couldn't Flatten component tree: {e.Message}");
|
||||||
|
}
|
||||||
|
} while (!done);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Update
|
||||||
|
|
||||||
|
internal bool GlobalLayoutChanged;
|
||||||
|
internal void LayoutChanged() => GlobalLayoutChanged = true;
|
||||||
|
private double LastUpdateTime;
|
||||||
|
private int UpdateLoopCount = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// Forses 1 layout update step, even when Frozen
|
||||||
|
/// </summary>
|
||||||
|
public void Step()
|
||||||
|
{
|
||||||
|
Update(LastUpdateTime + UpdateInterval, true, true);
|
||||||
|
}
|
||||||
|
public void Update(double totalTime, bool force = false, bool noInput = false)
|
||||||
|
{
|
||||||
|
if (!force)
|
||||||
|
{
|
||||||
|
if (Frozen) return;
|
||||||
|
if (totalTime - LastUpdateTime <= UpdateInterval) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CUIDebug.Flush();
|
||||||
|
|
||||||
|
if (TreeChanged)
|
||||||
|
{
|
||||||
|
OnTreeChanged?.Invoke();
|
||||||
|
|
||||||
|
FlattenTree();
|
||||||
|
TreeChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noInput) HandleInput(totalTime);
|
||||||
|
|
||||||
|
RunStraigth(c => c.InvokeOnUpdate(totalTime));
|
||||||
|
|
||||||
|
|
||||||
|
if (CalculateUntilResolved)
|
||||||
|
{
|
||||||
|
UpdateLoopCount = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
GlobalLayoutChanged = false;
|
||||||
|
|
||||||
|
if (TreeChanged)
|
||||||
|
{
|
||||||
|
OnTreeChanged?.Invoke();
|
||||||
|
|
||||||
|
FlattenTree();
|
||||||
|
TreeChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunReverse(c =>
|
||||||
|
{
|
||||||
|
c.Layout.ResizeToContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
RunStraigth(c =>
|
||||||
|
{
|
||||||
|
c.Layout.Update();
|
||||||
|
c.Layout.UpdateDecor();
|
||||||
|
});
|
||||||
|
|
||||||
|
UpdateLoopCount++;
|
||||||
|
if (UpdateLoopCount >= MaxLayoutRecalcLoopsPerUpdate)
|
||||||
|
{
|
||||||
|
PrintRecalLimitWarning();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (GlobalLayoutChanged);
|
||||||
|
//CUI.Log($"UpdateLoopCount: {UpdateLoopCount}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RunReverse(c =>
|
||||||
|
{
|
||||||
|
c.Layout.ResizeToContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
RunStraigth(c =>
|
||||||
|
{
|
||||||
|
c.Layout.Update();
|
||||||
|
c.Layout.UpdateDecor();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO do i need 2 updates?
|
||||||
|
//RunStraigth(c => c.InvokeOnUpdate(totalTime));
|
||||||
|
|
||||||
|
LastUpdateTime = totalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Draw
|
||||||
|
|
||||||
|
private void StopStart(SpriteBatch spriteBatch, Rectangle SRect, SamplerState? samplerState = null)
|
||||||
|
{
|
||||||
|
samplerState ??= GUI.SamplerState;
|
||||||
|
spriteBatch.End();
|
||||||
|
spriteBatch.GraphicsDevice.ScissorRectangle = SRect;
|
||||||
|
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: samplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
sw.Restart();
|
||||||
|
|
||||||
|
Rectangle OriginalSRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||||
|
Rectangle SRect = OriginalSRect;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RunStraigth(c =>
|
||||||
|
{
|
||||||
|
if (!c.Visible || c.CulledOut) return;
|
||||||
|
if (c.Parent != null && c.Parent.ScissorRect.HasValue && SRect != c.Parent.ScissorRect.Value)
|
||||||
|
{
|
||||||
|
SRect = c.Parent.ScissorRect.Value;
|
||||||
|
StopStart(spriteBatch, SRect, c.SamplerState);
|
||||||
|
}
|
||||||
|
c.Draw(spriteBatch);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (spriteBatch.GraphicsDevice.ScissorRectangle != OriginalSRect) StopStart(spriteBatch, OriginalSRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
RunStraigth(c =>
|
||||||
|
{
|
||||||
|
if (!c.Visible || c.CulledOut) return;
|
||||||
|
c.DrawFront(spriteBatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
// CUIDebug.EnsureCategory();
|
||||||
|
// CUIDebug.CaptureTicks(sw.ElapsedTicks, "CUI.Draw");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
// https://youtu.be/xuFgUmYCS8E?feature=shared&t=72
|
||||||
|
#region HandleInput Start
|
||||||
|
|
||||||
|
public void OnDragEnd(CUIDragHandle h) { if (h == GrabbedDragHandle) GrabbedDragHandle = null; }
|
||||||
|
public void OnResizeEnd(CUIResizeHandle h) { if (h == GrabbedResizeHandle) GrabbedResizeHandle = null; }
|
||||||
|
public void OnSwipeEnd(CUISwipeHandle h) { if (h == GrabbedSwipeHandle) GrabbedSwipeHandle = null; }
|
||||||
|
|
||||||
|
|
||||||
|
private void HandleInput(double totalTime)
|
||||||
|
{
|
||||||
|
HandleGlobal(totalTime);
|
||||||
|
HandleMouse(totalTime);
|
||||||
|
HandleKeyboard(totalTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGlobal(double totalTime)
|
||||||
|
{
|
||||||
|
if (CUI.Input.MouseDown) Global.InvokeOnMouseDown(CUI.Input);
|
||||||
|
if (CUI.Input.MouseUp)
|
||||||
|
{
|
||||||
|
Global.InvokeOnMouseUp(CUI.Input);
|
||||||
|
Global.InvokeOnClick(CUI.Input);
|
||||||
|
}
|
||||||
|
if (CUI.Input.MouseMoved) Global.InvokeOnMouseMoved(CUI.Input);
|
||||||
|
if (CUI.Input.SomeKeyPressed) Global.InvokeOnKeyDown(CUI.Input);
|
||||||
|
if (CUI.Input.SomeKeyUnpressed) Global.InvokeOnKeyUp(CUI.Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleKeyboard(double totalTime)
|
||||||
|
{
|
||||||
|
if (FocusedComponent == null) FocusedComponent = this;
|
||||||
|
if (CUI.Input.PressedKeys.Contains(Keys.Escape)) FocusedComponent = this;
|
||||||
|
if (CUI.Input.SomeKeyPressed) FocusedComponent.InvokeOnKeyDown(CUI.Input);
|
||||||
|
if (CUI.Input.SomeKeyUnpressed) FocusedComponent.InvokeOnKeyUp(CUI.Input);
|
||||||
|
if (CUI.Input.SomeWindowEvents) FocusedComponent.InvokeOnTextInput(CUI.Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleMouse(double totalTime)
|
||||||
|
{
|
||||||
|
if (!CUI.Input.SomethingHappened) return;
|
||||||
|
|
||||||
|
if (!CUI.Input.MouseHeld)
|
||||||
|
{
|
||||||
|
GrabbedDragHandle?.EndDrag();
|
||||||
|
GrabbedResizeHandle?.EndResize();
|
||||||
|
GrabbedSwipeHandle?.EndSwipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CUI.Input.MouseMoved)
|
||||||
|
{
|
||||||
|
GrabbedDragHandle?.DragTo(CUI.Input.MousePosition);
|
||||||
|
GrabbedResizeHandle?.Resize(CUI.Input.MousePosition);
|
||||||
|
GrabbedSwipeHandle?.Swipe(CUI.Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CUI.Input.MouseInputHandled) return;
|
||||||
|
|
||||||
|
//HACK
|
||||||
|
//if (CUI.Input.ClickConsumed) return;
|
||||||
|
|
||||||
|
//TODO think where should i put it?
|
||||||
|
if (GrabbedResizeHandle != null || GrabbedDragHandle != null || GrabbedSwipeHandle != null) return;
|
||||||
|
|
||||||
|
List<CUIComponent> prevMouseOnList = new List<CUIComponent>(MouseOnList);
|
||||||
|
|
||||||
|
CUIComponent CurrentMouseOn = null;
|
||||||
|
MouseOnList.Clear();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// form MouseOnList
|
||||||
|
// Note: including main component
|
||||||
|
if (
|
||||||
|
GUI.MouseOn == null || (GUI.MouseOn is GUIButton btn && btn.Text == "DUMMY")
|
||||||
|
|| (this == CUI.TopMain) //TODO guh
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RunStraigth(c =>
|
||||||
|
{
|
||||||
|
bool ok = !c.IgnoreEvents && c.Real.Contains(CUI.Input.MousePosition) && c.ShouldInvoke(CUI.Input);
|
||||||
|
|
||||||
|
if (c.Parent != null && c.Parent.ScissorRect.HasValue &&
|
||||||
|
!c.Parent.ScissorRect.Value.Contains(CUI.Input.Mouse.Position))
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok) MouseOnList.Add(c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseOn = MouseOnList.LastOrDefault();
|
||||||
|
|
||||||
|
//HACK
|
||||||
|
if (MouseOn != this)
|
||||||
|
{
|
||||||
|
CUI.Input.MouseInputHandled = true;
|
||||||
|
CUIMultiModResolver.MarkOtherInputsAsHandled();
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (CurrentMouseOn != null) GUI.MouseOn = dummyComponent;
|
||||||
|
|
||||||
|
|
||||||
|
foreach (CUIComponent c in prevMouseOnList)
|
||||||
|
{
|
||||||
|
c.MousePressed = false;
|
||||||
|
c.MouseOver = false;
|
||||||
|
c.InvokeOnMouseOff(CUI.Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CUIComponent c in MouseOnList)
|
||||||
|
{
|
||||||
|
c.MousePressed = CUI.Input.MouseHeld;
|
||||||
|
c.MouseOver = true;
|
||||||
|
c.InvokeOnMouseOn(CUI.Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse enter / leave
|
||||||
|
foreach (CUIComponent c in prevMouseOnList.Except(MouseOnList)) c.InvokeOnMouseLeave(CUI.Input);
|
||||||
|
foreach (CUIComponent c in MouseOnList.Except(prevMouseOnList)) c.InvokeOnMouseEnter(CUI.Input);
|
||||||
|
|
||||||
|
|
||||||
|
// focus
|
||||||
|
if (CUI.Input.MouseDown)
|
||||||
|
{
|
||||||
|
CUIComponent newFocused = this;
|
||||||
|
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (MouseOnList[i].FocusHandle.ShouldStart(CUI.Input))
|
||||||
|
{
|
||||||
|
newFocused = MouseOnList[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FocusedComponent = newFocused;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (MouseOnList[i].RightResizeHandle.ShouldStart(CUI.Input))
|
||||||
|
{
|
||||||
|
GrabbedResizeHandle = MouseOnList[i].RightResizeHandle;
|
||||||
|
GrabbedResizeHandle.BeginResize(CUI.Input.MousePosition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MouseOnList[i].LeftResizeHandle.ShouldStart(CUI.Input))
|
||||||
|
{
|
||||||
|
GrabbedResizeHandle = MouseOnList[i].LeftResizeHandle;
|
||||||
|
GrabbedResizeHandle.BeginResize(CUI.Input.MousePosition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (GrabbedResizeHandle != null) return;
|
||||||
|
|
||||||
|
//Scroll
|
||||||
|
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (CUI.Input.Scrolled) MouseOnList[i].InvokeOnScroll(CUI.Input);
|
||||||
|
|
||||||
|
if (MouseOnList[i].ConsumeMouseScroll) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Move
|
||||||
|
if (CUI.Input.MouseMoved)
|
||||||
|
{
|
||||||
|
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
MouseOnList[i].InvokeOnMouseMove(CUI.Input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Clicks
|
||||||
|
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (CUI.Input.MouseDown) MouseOnList[i].InvokeOnMouseDown(CUI.Input);
|
||||||
|
if (CUI.Input.MouseUp)
|
||||||
|
{
|
||||||
|
MouseOnList[i].InvokeOnMouseUp(CUI.Input);
|
||||||
|
MouseOnList[i].InvokeOnClick(CUI.Input);
|
||||||
|
}
|
||||||
|
if (CUI.Input.DoubleClick) MouseOnList[i].InvokeOnDClick(CUI.Input);
|
||||||
|
|
||||||
|
if (MouseOnList[i].ConsumeMouseClicks || CUI.Input.ClickConsumed) break;
|
||||||
|
}
|
||||||
|
if (CUI.Input.ClickConsumed) return;
|
||||||
|
|
||||||
|
// Swipe
|
||||||
|
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (MouseOnList[i].SwipeHandle.ShouldStart(CUI.Input))
|
||||||
|
{
|
||||||
|
GrabbedSwipeHandle = MouseOnList[i].SwipeHandle;
|
||||||
|
GrabbedSwipeHandle.BeginSwipe(CUI.Input.MousePosition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MouseOnList[i].ConsumeSwipe) break;
|
||||||
|
}
|
||||||
|
if (GrabbedSwipeHandle != null) return;
|
||||||
|
|
||||||
|
// Drag
|
||||||
|
for (int i = MouseOnList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (MouseOnList[i].DragHandle.ShouldStart(CUI.Input))
|
||||||
|
{
|
||||||
|
GrabbedDragHandle = MouseOnList[i].DragHandle;
|
||||||
|
GrabbedDragHandle.BeginDrag(CUI.Input.MousePosition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MouseOnList[i].ConsumeDragAndDrop) break;
|
||||||
|
}
|
||||||
|
if (GrabbedDragHandle != null) return;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
#region HandleInput End
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obsolete function
|
||||||
|
/// Will run generator func with this
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initFunc"> Generator function that adds components to passed Main </param>
|
||||||
|
public void Load(Action<CUIMainComponent> initFunc)
|
||||||
|
{
|
||||||
|
RemoveAllChildren();
|
||||||
|
initFunc(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIMainComponent() : base()
|
||||||
|
{
|
||||||
|
CullChildren = true;
|
||||||
|
Real = new CUIRect(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||||
|
Visible = false;
|
||||||
|
//IgnoreEvents = true;
|
||||||
|
ShouldPassPropsToChildren = false;
|
||||||
|
|
||||||
|
|
||||||
|
Debug = true;
|
||||||
|
ChildrenBoundaries = CUIBoundaries.Box;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintRecalLimitWarning()
|
||||||
|
{
|
||||||
|
CUI.Log($"Warning: Your GUI code requires {MaxLayoutRecalcLoopsPerUpdate} layout update loops to fully resolve (which is cringe). Optimize it!", Color.Orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
244
Quick Interactions/CSharp/Client/CrabUI/Components/CUIMap.cs
Normal file
244
Quick Interactions/CSharp/Client/CrabUI/Components/CUIMap.cs
Normal 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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Button with multiple options
|
||||||
|
/// which are rotating when you click
|
||||||
|
/// </summary>
|
||||||
|
public class CUIMultiButton : CUIButton
|
||||||
|
{
|
||||||
|
private List<string> options = new List<string>();
|
||||||
|
/// <summary>
|
||||||
|
/// Options are just strings
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public IEnumerable<string> Options
|
||||||
|
{
|
||||||
|
get => options;
|
||||||
|
set => options = value.ToList();
|
||||||
|
}
|
||||||
|
public event Action<string> OnSelect;
|
||||||
|
public Action<string> AddOnSelect { set { OnSelect += value; } }
|
||||||
|
|
||||||
|
public bool CycleOnClick { get; set; } = true;
|
||||||
|
public int SelectedIndex
|
||||||
|
{
|
||||||
|
get => options.IndexOf(Selected);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (options.Count == 0)
|
||||||
|
{
|
||||||
|
Selected = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Selected = options.ElementAtOrDefault(value % options.Count) ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[CUISerializable]
|
||||||
|
public string Selected
|
||||||
|
{
|
||||||
|
get => Text;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Text = value;
|
||||||
|
OnSelect?.Invoke(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string option) => options.Add(option);
|
||||||
|
public void Remove(string option)
|
||||||
|
{
|
||||||
|
int i = options.IndexOf(option);
|
||||||
|
options.Remove(option);
|
||||||
|
if (option == Selected) Select(i);
|
||||||
|
}
|
||||||
|
public void Select(int i) => SelectedIndex = i;
|
||||||
|
public void Select(string option) => Selected = option;
|
||||||
|
public void SelectNext() => SelectedIndex++;
|
||||||
|
public void SelectPrev() => SelectedIndex--;
|
||||||
|
|
||||||
|
public CUIMultiButton() : base()
|
||||||
|
{
|
||||||
|
Text = "MultiButton";
|
||||||
|
OnMouseDown += (e) =>
|
||||||
|
{
|
||||||
|
if (CycleOnClick)
|
||||||
|
{
|
||||||
|
SelectNext();
|
||||||
|
if (Command != null) DispatchUp(new CUICommand(Command, Selected));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CUITextBlock DoWrapFor but for all text
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected override Vector2 DoWrapFor(Vector2 size)
|
||||||
|
{
|
||||||
|
if ((!WrappedForThisSize.HasValue || size == WrappedForThisSize.Value) && !TextPropChanged) return WrappedSize;
|
||||||
|
|
||||||
|
TextPropChanged = false;
|
||||||
|
WrappedForThisSize = size;
|
||||||
|
|
||||||
|
if (Vertical) size = new Vector2(0, size.Y);
|
||||||
|
|
||||||
|
|
||||||
|
IEnumerable<string> WrappedTexts;
|
||||||
|
if (Wrap || Vertical)
|
||||||
|
{
|
||||||
|
WrappedText = Font.WrapText(Text, size.X / TextScale - Padding.X * 2).Trim('\n');
|
||||||
|
WrappedTexts = options.Select(o => Font.WrapText(o, size.X / TextScale - Padding.X * 2).Trim('\n'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WrappedText = Text;
|
||||||
|
WrappedTexts = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Vector2> RealTextSizes = WrappedTexts.Select(t => Font.MeasureString(t) * TextScale);
|
||||||
|
|
||||||
|
float maxX = 0;
|
||||||
|
float maxY = 0;
|
||||||
|
foreach (Vector2 s in RealTextSizes)
|
||||||
|
{
|
||||||
|
if (s.X > maxX) maxX = s.X;
|
||||||
|
if (s.Y > maxY) maxY = s.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 MaxTextSize = new Vector2(maxX, maxY);
|
||||||
|
|
||||||
|
RealTextSize = Font.MeasureString(WrappedText) * TextScale;
|
||||||
|
|
||||||
|
if (WrappedText == "") RealTextSize = new Vector2(0, 0);
|
||||||
|
RealTextSize = new Vector2((float)Math.Round(RealTextSize.X), (float)Math.Round(RealTextSize.Y));
|
||||||
|
|
||||||
|
Vector2 minSize = MaxTextSize + Padding * 2;
|
||||||
|
|
||||||
|
if (!Wrap || Vertical)
|
||||||
|
{
|
||||||
|
SetForcedMinSize(new CUINullVector2(minSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedSize = new Vector2(Math.Max(size.X, minSize.X), Math.Max(size.Y, minSize.Y));
|
||||||
|
|
||||||
|
return WrappedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override Vector2 AmIOkWithThisSize(Vector2 size)
|
||||||
|
{
|
||||||
|
return DoWrapFor(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Container for other components
|
||||||
|
/// Can have only 1 child
|
||||||
|
/// Sets component as it's only child when you open it (as a page)
|
||||||
|
/// </summary>
|
||||||
|
public class CUIPages : CUIComponent
|
||||||
|
{
|
||||||
|
public CUIComponent OpenedPage;
|
||||||
|
|
||||||
|
public bool IsOpened(CUIComponent p) => OpenedPage == p;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds page as its only child
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page"></param>
|
||||||
|
public void Open(CUIComponent page)
|
||||||
|
{
|
||||||
|
RemoveAllChildren();
|
||||||
|
Append(page);
|
||||||
|
page.Relative = new CUINullRect(0, 0, 1, 1);
|
||||||
|
OpenedPage = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIPages() : base()
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.Transparent;
|
||||||
|
Border.Color = Color.Transparent;
|
||||||
|
CullChildren = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Passive block of text
|
||||||
|
/// </summary>
|
||||||
|
public class CUITextBlock : CUIComponent
|
||||||
|
{
|
||||||
|
public event Action OnTextChanged;
|
||||||
|
public Action AddOnTextChanged { set { OnTextChanged += value; } }
|
||||||
|
|
||||||
|
|
||||||
|
//TODO move padding here, it makes no sense in CUIComponent
|
||||||
|
private bool wrap;
|
||||||
|
[CUISerializable]
|
||||||
|
public bool Wrap
|
||||||
|
{
|
||||||
|
get => wrap;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
wrap = value;
|
||||||
|
MeasureUnwrapped();
|
||||||
|
TextPropChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[CUISerializable] public Color TextColor { get; set; }
|
||||||
|
private GUIFont font = GUIStyle.Font;
|
||||||
|
[CUISerializable]
|
||||||
|
public GUIFont Font
|
||||||
|
{
|
||||||
|
get => font;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
font = value;
|
||||||
|
MeasureUnwrapped();
|
||||||
|
TextPropChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// A Vector2 ([0..1],[0..1])
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public Vector2 TextAlign { get; set; }
|
||||||
|
[CUISerializable] public bool Vertical { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Lil optimization: ghost text won't set forsed size and parent won't be able to fit to it
|
||||||
|
/// But it will increase performance in large lists
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool GhostText { get; set; }
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public string Text { get => text; set => SetText(value); }
|
||||||
|
[CUISerializable]
|
||||||
|
public float TextScale { get => textScale; set => SetTextScale(value); }
|
||||||
|
|
||||||
|
#region Cringe
|
||||||
|
protected Vector2 RealTextSize;
|
||||||
|
[Calculated] protected Vector2 TextDrawPos { get; set; }
|
||||||
|
[Calculated] protected string WrappedText { get; set; } = "";
|
||||||
|
protected Vector2? WrappedForThisSize;
|
||||||
|
[Calculated] protected Vector2 WrappedSize { get; set; }
|
||||||
|
public Vector2 UnwrappedTextSize { get; set; }
|
||||||
|
public Vector2 UnwrappedMinSize { get; set; }
|
||||||
|
protected bool TextPropChanged;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected string text = ""; internal void SetText(string value)
|
||||||
|
{
|
||||||
|
text = value ?? "";
|
||||||
|
OnTextChanged?.Invoke();
|
||||||
|
|
||||||
|
MeasureUnwrapped();
|
||||||
|
TextPropChanged = true;
|
||||||
|
OnPropChanged();
|
||||||
|
OnAbsolutePropChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float textScale = 0.9f; internal void SetTextScale(float value)
|
||||||
|
{
|
||||||
|
textScale = value;
|
||||||
|
MeasureUnwrapped();
|
||||||
|
TextPropChanged = true;
|
||||||
|
OnPropChanged();
|
||||||
|
OnAbsolutePropChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Note: works only on unwrapped text for now because WrappedText is delayed
|
||||||
|
/// <summary>
|
||||||
|
/// X coordinate of caret if there was one
|
||||||
|
/// Used in CUITextInput, you don't need this
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="i"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public float CaretPos(int i)
|
||||||
|
{
|
||||||
|
return Font.MeasureString(Text.SubstringSafe(0, i)).X * TextScale + Padding.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Note: works only on unwrapped text for now because WrappedText is delayed
|
||||||
|
/// <summary>
|
||||||
|
/// Tndex of caret if there was one
|
||||||
|
/// Used in CUITextInput, you don't need this
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="i"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int CaretIndex(float x)
|
||||||
|
{
|
||||||
|
int Aprox = (int)Math.Round((x - Padding.X) / Font.MeasureString(Text).X * Text.Length);
|
||||||
|
|
||||||
|
int closestCaretPos = Aprox;
|
||||||
|
float smallestDif = Math.Abs(x - CaretPos(Aprox));
|
||||||
|
|
||||||
|
for (int i = Aprox - 2; i <= Aprox + 2; i++)
|
||||||
|
{
|
||||||
|
float dif = Math.Abs(x - CaretPos(i));
|
||||||
|
if (dif < smallestDif)
|
||||||
|
{
|
||||||
|
closestCaretPos = i;
|
||||||
|
smallestDif = dif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestCaretPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small optimisation, doesn't seem to save much
|
||||||
|
protected virtual void MeasureUnwrapped()
|
||||||
|
{
|
||||||
|
UnwrappedTextSize = Font.MeasureString(Text) * TextScale;
|
||||||
|
UnwrappedMinSize = UnwrappedTextSize + Padding * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Vector2 DoWrapFor(Vector2 size)
|
||||||
|
{
|
||||||
|
// To prevent loop
|
||||||
|
if (!(WrappedForThisSize.HasValue && WrappedForThisSize != size) && !TextPropChanged) return WrappedSize;
|
||||||
|
|
||||||
|
TextPropChanged = false;
|
||||||
|
WrappedForThisSize = size;
|
||||||
|
|
||||||
|
// There's no way to wrap vertical text
|
||||||
|
bool isInWrapZone = Vertical ? false : size.X <= UnwrappedMinSize.X;
|
||||||
|
bool isSolid = Vertical || !Wrap;
|
||||||
|
|
||||||
|
if (Vertical) size = new Vector2(0, size.Y);
|
||||||
|
|
||||||
|
if ((Wrap && isInWrapZone) || Vertical)
|
||||||
|
{
|
||||||
|
WrappedText = Font.WrapText(Text, size.X / TextScale - Padding.X * 2).Trim('\n');
|
||||||
|
RealTextSize = Font.MeasureString(WrappedText) * TextScale;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WrappedText = Text;
|
||||||
|
RealTextSize = UnwrappedTextSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WrappedText == "") RealTextSize = new Vector2(0, 0);
|
||||||
|
|
||||||
|
RealTextSize = new Vector2((float)Math.Round(RealTextSize.X), (float)Math.Round(RealTextSize.Y));
|
||||||
|
|
||||||
|
Vector2 minSize = RealTextSize + Padding * 2;
|
||||||
|
|
||||||
|
if (isSolid && !GhostText)
|
||||||
|
{
|
||||||
|
SetForcedMinSize(new CUINullVector2(minSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedSize = new Vector2(Math.Max(size.X, minSize.X), Math.Max(size.Y, minSize.Y));
|
||||||
|
|
||||||
|
return WrappedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override Vector2 AmIOkWithThisSize(Vector2 size)
|
||||||
|
{
|
||||||
|
return DoWrapFor(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Note: This is a bottleneck for large lists of text
|
||||||
|
internal override void UpdatePseudoChildren()
|
||||||
|
{
|
||||||
|
if (CulledOut) return;
|
||||||
|
TextDrawPos = CUIAnchor.GetChildPos(Real, TextAlign, Vector2.Zero, RealTextSize / Scale) + Padding * CUIAnchor.Direction(TextAlign) / Scale;
|
||||||
|
|
||||||
|
//CUIDebug.Capture(null, this, "UpdatePseudoChildren", "", "TextDrawPos", $"{TextDrawPos - Real.Position}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
base.Draw(spriteBatch);
|
||||||
|
|
||||||
|
// Font.DrawString(spriteBatch, WrappedText, TextDrawPos, TextColor, rotation: 0, origin: Vector2.Zero, TextScale, spriteEffects: SpriteEffects.None, layerDepth: 0.1f);
|
||||||
|
|
||||||
|
Font.Value.DrawString(spriteBatch, WrappedText, TextDrawPos, TextColor, rotation: 0, origin: Vector2.Zero, TextScale / Scale, se: SpriteEffects.None, layerDepth: 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUITextBlock() { }
|
||||||
|
|
||||||
|
public CUITextBlock(string text) : this()
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,479 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using EventInput;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text input
|
||||||
|
/// </summary>
|
||||||
|
public class CUITextInput : CUIComponent, IKeyboardSubscriber
|
||||||
|
{
|
||||||
|
|
||||||
|
#region IKeyboardSubscriber
|
||||||
|
|
||||||
|
private Keys pressedKey;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// From IKeyboardSubscriber, don't use it directly
|
||||||
|
/// </summary>
|
||||||
|
public void ReceiveSpecialInput(Keys key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pressedKey = key;
|
||||||
|
if (key == Keys.Back) Back();
|
||||||
|
if (key == Keys.Delete) Delete();
|
||||||
|
if (key == Keys.Left) MoveLeft();
|
||||||
|
if (key == Keys.Right) MoveRight();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// From IKeyboardSubscriber, don't use it directly
|
||||||
|
/// </summary>
|
||||||
|
public void ReceiveTextInput(char inputChar) => ReceiveTextInput(inputChar.ToString());
|
||||||
|
/// <summary>
|
||||||
|
/// From IKeyboardSubscriber, don't use it directly
|
||||||
|
/// </summary>
|
||||||
|
public void ReceiveTextInput(string text)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CutSelection();
|
||||||
|
Text = Text.Insert(CaretPos, text);
|
||||||
|
CaretPos = CaretPos + 1;
|
||||||
|
OnTextAdded?.Invoke(text);
|
||||||
|
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||||
|
//CUI.Log($"ReceiveTextInput {text}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// From IKeyboardSubscriber, don't use it directly
|
||||||
|
/// </summary>
|
||||||
|
public void ReceiveCommandInput(char command)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (pressedKey == Keys.A) SelectAll();
|
||||||
|
if (pressedKey == Keys.C) Copy();
|
||||||
|
if (pressedKey == Keys.V) Paste();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning(e);
|
||||||
|
}
|
||||||
|
//CUI.Log($"ReceiveCommandInput {command}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Alt+tab?
|
||||||
|
/// <summary>
|
||||||
|
/// From IKeyboardSubscriber, don't use it directly
|
||||||
|
/// </summary>
|
||||||
|
public void ReceiveEditingInput(string text, int start, int length)
|
||||||
|
{
|
||||||
|
//CUI.Log($"ReceiveEditingInput {text} {start} {length}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO mb should lose focus here
|
||||||
|
/// <summary>
|
||||||
|
/// From IKeyboardSubscriber, don't use it directly
|
||||||
|
/// </summary>
|
||||||
|
public bool Selected { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Commands
|
||||||
|
public void SelectAll() => Select(0, Text.Length);
|
||||||
|
|
||||||
|
public void Copy()
|
||||||
|
{
|
||||||
|
if (Selection.IsEmpty) return;
|
||||||
|
selectionHandle.Grabbed = false;
|
||||||
|
Clipboard.SetText(Text.SubstringSafe(Selection.Start, Selection.End));
|
||||||
|
}
|
||||||
|
public void Paste()
|
||||||
|
{
|
||||||
|
ReceiveTextInput(Clipboard.GetText());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddText(string text) => ReceiveTextInput(text);
|
||||||
|
public void MoveLeft()
|
||||||
|
{
|
||||||
|
CaretPos--;
|
||||||
|
Selection = IntRange.Zero;
|
||||||
|
}
|
||||||
|
public void MoveRight()
|
||||||
|
{
|
||||||
|
CaretPos++;
|
||||||
|
Selection = IntRange.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
// //TODO
|
||||||
|
// public void SelectLeft()
|
||||||
|
// {
|
||||||
|
// if (Selection == IntRange.Zero) Selection = new IntRange(CaretPos - 1, CaretPos);
|
||||||
|
// else Selection = new IntRange(Selection.Start - 1, Selection.End);
|
||||||
|
// }
|
||||||
|
// //TODO
|
||||||
|
// public void SelectRight()
|
||||||
|
// {
|
||||||
|
// if (Selection == IntRange.Zero) Selection = new IntRange(CaretPos, CaretPos + 1);
|
||||||
|
// else Selection = new IntRange(Selection.Start, Selection.End + 1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void Back()
|
||||||
|
{
|
||||||
|
TextInputState oldState = State;
|
||||||
|
if (!Selection.IsEmpty) CutSelection();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos - 1);
|
||||||
|
string s2 = oldState.Text.SubstringSafe(oldState.CaretPos);
|
||||||
|
Text = s1 + s2;
|
||||||
|
CaretPos = oldState.CaretPos - 1;
|
||||||
|
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete()
|
||||||
|
{
|
||||||
|
TextInputState oldState = State;
|
||||||
|
if (!Selection.IsEmpty) CutSelection();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos);
|
||||||
|
string s2 = oldState.Text.SubstringSafe(oldState.CaretPos + 1);
|
||||||
|
Text = s1 + s2;
|
||||||
|
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||||
|
//CaretPos = oldState.CaretPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CutSelection()
|
||||||
|
{
|
||||||
|
if (Selection.IsEmpty) return;
|
||||||
|
selectionHandle.Grabbed = false;
|
||||||
|
string s1 = Text.SubstringSafe(0, Selection.Start);
|
||||||
|
string s2 = Text.SubstringSafe(Selection.End);
|
||||||
|
Text = s1 + s2;
|
||||||
|
CaretPos = Selection.Start;
|
||||||
|
Selection = IntRange.Zero;
|
||||||
|
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int SetCaretPos(Vector2 v)
|
||||||
|
{
|
||||||
|
int newCaretPos = TextComponent.CaretIndex(v.X);
|
||||||
|
CaretPos = newCaretPos;
|
||||||
|
return newCaretPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
internal class SelectionHandle
|
||||||
|
{
|
||||||
|
public bool Grabbed;
|
||||||
|
public int lastSelectedPos;
|
||||||
|
}
|
||||||
|
internal SelectionHandle selectionHandle = new SelectionHandle();
|
||||||
|
|
||||||
|
internal record struct TextInputState(string Text, IntRange Selection, int CaretPos)
|
||||||
|
{
|
||||||
|
public string Text { get; init; } = Text ?? "";
|
||||||
|
}
|
||||||
|
private TextInputState state; internal TextInputState State
|
||||||
|
{
|
||||||
|
get => state;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
state = ValidateState(value);
|
||||||
|
ApplyState(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TextInputState ValidateState(TextInputState state)
|
||||||
|
{
|
||||||
|
//return state with { CaretPos = state.CaretPos.Fit(0, state.Text.Length - 1) };
|
||||||
|
|
||||||
|
string newText = state.Text;
|
||||||
|
|
||||||
|
IntRange newSelection = new IntRange(
|
||||||
|
state.Selection.Start.Fit(0, newText.Length),
|
||||||
|
state.Selection.End.Fit(0, newText.Length)
|
||||||
|
);
|
||||||
|
|
||||||
|
int newCaretPos = state.CaretPos.Fit(0, newText.Length);
|
||||||
|
|
||||||
|
return new TextInputState(newText, newSelection, newCaretPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApplyState(TextInputState state)
|
||||||
|
{
|
||||||
|
TextComponent.Text = state.Text;
|
||||||
|
|
||||||
|
SelectionOverlay.Visible = !state.Selection.IsEmpty;
|
||||||
|
CaretIndicatorVisible = Focused && !SelectionOverlay.Visible;
|
||||||
|
|
||||||
|
if (!state.Selection.IsEmpty)
|
||||||
|
{
|
||||||
|
SelectionOverlay.Absolute = SelectionOverlay.Absolute with
|
||||||
|
{
|
||||||
|
Left = TextComponent.CaretPos(state.Selection.Start),
|
||||||
|
Width = TextComponent.CaretPos(state.Selection.End) - TextComponent.CaretPos(state.Selection.Start),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
CaretIndicator.Absolute = CaretIndicator.Absolute with
|
||||||
|
{
|
||||||
|
Left = TextComponent.CaretPos(state.CaretPos),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private bool valid = true; public bool Valid
|
||||||
|
{
|
||||||
|
get => valid;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (valid == value) return;
|
||||||
|
valid = value;
|
||||||
|
UpdateBorderColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type VatidationType { get; set; }
|
||||||
|
public bool IsValidText(string text)
|
||||||
|
{
|
||||||
|
if (VatidationType == null) return true;
|
||||||
|
|
||||||
|
if (VatidationType == typeof(string)) return true;
|
||||||
|
if (VatidationType == typeof(Color)) return true;
|
||||||
|
if (VatidationType == typeof(bool)) return bool.TryParse(text, out _);
|
||||||
|
if (VatidationType == typeof(int)) return int.TryParse(text, out _);
|
||||||
|
if (VatidationType == typeof(float)) return float.TryParse(text, out _);
|
||||||
|
if (VatidationType == typeof(double)) return double.TryParse(text, out _);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO this is cringe
|
||||||
|
// public override void Consume(object o)
|
||||||
|
// {
|
||||||
|
// string value = (string)o;
|
||||||
|
// State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||||
|
// Valid = IsValidText(value);
|
||||||
|
// }
|
||||||
|
|
||||||
|
internal CUITextBlock TextComponent;
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => State.Text;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Disabled) return;
|
||||||
|
|
||||||
|
State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||||
|
|
||||||
|
bool isvalid = IsValidText(value);
|
||||||
|
if (isvalid)
|
||||||
|
{
|
||||||
|
OnTextChanged?.Invoke(State.Text);
|
||||||
|
}
|
||||||
|
Valid = isvalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal CUIComponent SelectionOverlay;
|
||||||
|
public IntRange Selection
|
||||||
|
{
|
||||||
|
get => State.Selection;
|
||||||
|
set => State = new TextInputState(State.Text, value, State.CaretPos);
|
||||||
|
}
|
||||||
|
public void Select(int start, int end) => Selection = new IntRange(start, end);
|
||||||
|
|
||||||
|
|
||||||
|
public bool CaretIndicatorVisible { get; set; }
|
||||||
|
public double CaretBlinkInterval { get; set; } = 0.5;
|
||||||
|
internal CUIComponent CaretIndicator;
|
||||||
|
public int CaretPos
|
||||||
|
{
|
||||||
|
get => State.CaretPos;
|
||||||
|
set => State = new TextInputState(State.Text, State.Selection, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
//[CUISerializable] public bool PreventOverflow { get; set; } = false;
|
||||||
|
|
||||||
|
public void UpdateBorderColor()
|
||||||
|
{
|
||||||
|
if (Valid)
|
||||||
|
{
|
||||||
|
if (Focused)
|
||||||
|
{
|
||||||
|
Style["Border"] = "CUIPalette.Input.Focused";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Style["Border"] = "CUIPalette.Input.Border";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Style["Border"] = "CUIPalette.Input.Invalid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[CUISerializable]
|
||||||
|
public float TextScale
|
||||||
|
{
|
||||||
|
get => TextComponent?.TextScale ?? 0;
|
||||||
|
set => TextComponent.TextScale = value;
|
||||||
|
}
|
||||||
|
public Color TextColor
|
||||||
|
{
|
||||||
|
get => TextComponent?.TextColor ?? Color.White;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (TextComponent != null)
|
||||||
|
{
|
||||||
|
TextComponent.TextColor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<string> OnTextChanged;
|
||||||
|
public Action<string> AddOnTextChanged { set => OnTextChanged += value; }
|
||||||
|
public event Action<string> OnTextAdded;
|
||||||
|
public Action<string> AddOnTextAdded { set => OnTextAdded += value; }
|
||||||
|
|
||||||
|
public override void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (Focused)
|
||||||
|
{
|
||||||
|
CaretIndicator.Visible = CaretIndicatorVisible && Timing.TotalTime % CaretBlinkInterval > CaretBlinkInterval / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
base.Draw(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public CUITextInput(string text) : this()
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
public CUITextInput() : base()
|
||||||
|
{
|
||||||
|
AbsoluteMin = new CUINullRect(w: 50, h: 22);
|
||||||
|
FitContent = new CUIBool2(true, true);
|
||||||
|
Focusable = true;
|
||||||
|
Border.Thickness = 2;
|
||||||
|
HideChildrenOutsideFrame = true;
|
||||||
|
ConsumeMouseClicks = true;
|
||||||
|
ConsumeDragAndDrop = true;
|
||||||
|
ConsumeSwipe = true;
|
||||||
|
BreakSerialization = true;
|
||||||
|
|
||||||
|
this["TextComponent"] = TextComponent = new CUITextBlock()
|
||||||
|
{
|
||||||
|
Text = "",
|
||||||
|
Relative = new CUINullRect(0, 0, 1, 1),
|
||||||
|
TextAlign = CUIAnchor.CenterLeft,
|
||||||
|
Style = new CUIStyle(){
|
||||||
|
{"Padding", "[2,2]"},
|
||||||
|
{"TextColor", "CUIPalette.Input.Text"},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this["SelectionOverlay"] = SelectionOverlay = new CUIComponent()
|
||||||
|
{
|
||||||
|
Style = new CUIStyle(){
|
||||||
|
{"BackgroundColor", "CUIPalette.Input.Selection"},
|
||||||
|
{"Border", "Transparent"},
|
||||||
|
},
|
||||||
|
Relative = new CUINullRect(h: 1),
|
||||||
|
Ghost = new CUIBool2(true, true),
|
||||||
|
IgnoreParentVisibility = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
this["CaretIndicator"] = CaretIndicator = new CUIComponent()
|
||||||
|
{
|
||||||
|
Style = new CUIStyle(){
|
||||||
|
{"BackgroundColor", "CUIPalette.Input.Caret"},
|
||||||
|
{"Border", "Transparent"},
|
||||||
|
},
|
||||||
|
Relative = new CUINullRect(y: 0.1f, h: 0.8f),
|
||||||
|
Absolute = new CUINullRect(w: 1),
|
||||||
|
Ghost = new CUIBool2(true, true),
|
||||||
|
Visible = false,
|
||||||
|
IgnoreParentVisibility = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
OnConsume += (o) =>
|
||||||
|
{
|
||||||
|
string value = o.ToString();
|
||||||
|
State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||||
|
Valid = IsValidText(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OnFocus += () =>
|
||||||
|
{
|
||||||
|
UpdateBorderColor();
|
||||||
|
CaretIndicator.Visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
OnFocusLost += () =>
|
||||||
|
{
|
||||||
|
UpdateBorderColor();
|
||||||
|
Selection = IntRange.Zero;
|
||||||
|
CaretIndicator.Visible = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
OnMouseDown += (e) =>
|
||||||
|
{
|
||||||
|
int newCaretPos = SetCaretPos(e.MousePosition - Real.Position);
|
||||||
|
Selection = IntRange.Zero;
|
||||||
|
selectionHandle.lastSelectedPos = newCaretPos;
|
||||||
|
selectionHandle.Grabbed = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
OnMouseMove += (e) =>
|
||||||
|
{
|
||||||
|
if (selectionHandle.Grabbed)
|
||||||
|
{
|
||||||
|
int nextCaretPos = SetCaretPos(e.MousePosition - Real.Position);
|
||||||
|
Selection = new IntRange(selectionHandle.lastSelectedPos, nextCaretPos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
OnDClick += (e) => SelectAll();
|
||||||
|
|
||||||
|
if (CUI.Main is not null)
|
||||||
|
{
|
||||||
|
CUI.Main.Global.OnMouseUp += (e) => selectionHandle.Grabbed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A button which can be on and off
|
||||||
|
/// It's derived from CUITextBlock and has all its props
|
||||||
|
/// </summary>
|
||||||
|
public class CUIToggleButton : CUITextBlock
|
||||||
|
{
|
||||||
|
[CUISerializable]
|
||||||
|
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
|
||||||
|
|
||||||
|
[CUISerializable] public Color DisabledColor { get; set; }
|
||||||
|
[CUISerializable] public Color OnColor { get; set; }
|
||||||
|
[CUISerializable] public Color OnHoverColor { get; set; }
|
||||||
|
[CUISerializable] public Color OffColor { get; set; }
|
||||||
|
[CUISerializable] public Color OffHoverColor { get; set; }
|
||||||
|
|
||||||
|
public Color MasterColor
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
OffColor = value.Multiply(0.5f);
|
||||||
|
OffHoverColor = value;
|
||||||
|
OnColor = value.Multiply(0.9f);
|
||||||
|
OnHoverColor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color MasterColorOpaque
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
OffColor = new Color((int)(value.R * 0.5f), (int)(value.G * 0.5f), (int)(value.B * 0.5f), value.A);
|
||||||
|
OffHoverColor = value;
|
||||||
|
OnColor = new Color((int)(value.R * 0.9f), (int)(value.G * 0.9f), (int)(value.B * 0.9f), value.A); ;
|
||||||
|
OnHoverColor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackgroundColor is used in base.Draw, but here it's calculated from OnColor/OffColor
|
||||||
|
// so it's not a prop anymore, and i don't want to serialize it
|
||||||
|
public new Color BackgroundColor { get => CUIProps.BackgroundColor.Value; set => CUIProps.BackgroundColor.SetValue(value); }
|
||||||
|
|
||||||
|
|
||||||
|
private string onText;
|
||||||
|
private string offText;
|
||||||
|
[CUISerializable]
|
||||||
|
public string OnText
|
||||||
|
{
|
||||||
|
get => onText;
|
||||||
|
set { onText = value; if (State && onText != null) Text = onText; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public string OffText
|
||||||
|
{
|
||||||
|
get => offText;
|
||||||
|
set { offText = value; if (!State && offText != null) Text = offText; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<bool> OnStateChange;
|
||||||
|
public Action<bool> AddOnStateChange { set { OnStateChange += value; } }
|
||||||
|
|
||||||
|
|
||||||
|
protected bool state;
|
||||||
|
[CUISerializable]
|
||||||
|
public bool State
|
||||||
|
{
|
||||||
|
get => state;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
state = value;
|
||||||
|
if (state && OnText != null) Text = OnText;
|
||||||
|
if (!state && OffText != null) Text = OffText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (Disabled)
|
||||||
|
{
|
||||||
|
BackgroundColor = DisabledColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (State)
|
||||||
|
{
|
||||||
|
if (MouseOver) BackgroundColor = OnHoverColor;
|
||||||
|
else BackgroundColor = OnColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (MouseOver) BackgroundColor = OffHoverColor;
|
||||||
|
else BackgroundColor = OffColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Draw(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIToggleButton() : base()
|
||||||
|
{
|
||||||
|
ConsumeMouseClicks = true;
|
||||||
|
ConsumeDragAndDrop = true;
|
||||||
|
ConsumeSwipe = true;
|
||||||
|
|
||||||
|
//BackgroundColor = OffColor;
|
||||||
|
|
||||||
|
TextAlign = new Vector2(0.5f, 0.5f);
|
||||||
|
Padding = new Vector2(4, 2);
|
||||||
|
|
||||||
|
Text = nameof(CUIToggleButton);
|
||||||
|
|
||||||
|
OnMouseDown += (e) =>
|
||||||
|
{
|
||||||
|
if (!Disabled)
|
||||||
|
{
|
||||||
|
State = !State;
|
||||||
|
SoundPlayer.PlayUISound(ClickSound);
|
||||||
|
OnStateChange?.Invoke(State);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIToggleButton(string text) : this()
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resizing components to it's Width and placing them sequentially
|
||||||
|
/// </summary>
|
||||||
|
public class CUIVerticalList : CUIComponent
|
||||||
|
{
|
||||||
|
[CUISerializable] public bool Scrollable { get; set; }
|
||||||
|
[CUISerializable] public float ScrollSpeed { get; set; } = 1.0f;
|
||||||
|
|
||||||
|
[CUISerializable] public float TopGap { get; set; } = 0;
|
||||||
|
[CUISerializable] public float BottomGap { get; set; } = 10f;
|
||||||
|
|
||||||
|
public override CUILayout Layout
|
||||||
|
{
|
||||||
|
get => layout;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
layout = new CUILayoutVerticalList();
|
||||||
|
layout.Host = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public CUILayoutVerticalList ListLayout => (CUILayoutVerticalList)Layout;
|
||||||
|
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public CUIDirection Direction
|
||||||
|
{
|
||||||
|
get => ListLayout.Direction;
|
||||||
|
set => ListLayout.Direction = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO test, sync with hlist
|
||||||
|
[CUISerializable]
|
||||||
|
public float Gap
|
||||||
|
{
|
||||||
|
get => ListLayout.Gap;
|
||||||
|
set => ListLayout.Gap = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CUISerializable]
|
||||||
|
public bool ResizeToHostWidth
|
||||||
|
{
|
||||||
|
get => ListLayout.ResizeToHostWidth;
|
||||||
|
set => ListLayout.ResizeToHostWidth = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Scroll
|
||||||
|
{
|
||||||
|
get => ChildrenOffset.Y;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!Scrollable) return;
|
||||||
|
CUIProps.ChildrenOffset.SetValue(
|
||||||
|
ChildrenOffset with { Y = value }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override CUIBoundaries ChildOffsetBounds => new CUIBoundaries(
|
||||||
|
minX: 0,
|
||||||
|
maxX: 0,
|
||||||
|
maxY: TopGap,
|
||||||
|
minY: Math.Min(Real.Height - ListLayout.TotalHeight - BottomGap, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
public CUIVerticalList() : base()
|
||||||
|
{
|
||||||
|
CullChildren = true;
|
||||||
|
|
||||||
|
|
||||||
|
OnScroll += (m) => Scroll += m.Scroll * ScrollSpeed;
|
||||||
|
|
||||||
|
ChildrenBoundaries = CUIBoundaries.VerticalTube;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simple dialog box with a message and ok button
|
||||||
|
/// use public static void Open(string msg) to open it
|
||||||
|
/// </summary>
|
||||||
|
public class CUIMessageBox : CUIFrame
|
||||||
|
{
|
||||||
|
public static void Open(string msg)
|
||||||
|
{
|
||||||
|
CUI.TopMain.Append(new CUIMessageBox(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CUIMessageBox(string msg) : base()
|
||||||
|
{
|
||||||
|
Palette = PaletteOrder.Quaternary;
|
||||||
|
Resizible = false;
|
||||||
|
|
||||||
|
Relative = new CUINullRect(0, 0, 0.25f, 0.25f);
|
||||||
|
Anchor = CUIAnchor.Center;
|
||||||
|
|
||||||
|
OutlineThickness = 2;
|
||||||
|
|
||||||
|
this["layout"] = new CUIVerticalList()
|
||||||
|
{
|
||||||
|
Relative = new CUINullRect(0, 0, 1, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
this["layout"]["main"] = new CUIVerticalList()
|
||||||
|
{
|
||||||
|
FillEmptySpace = new CUIBool2(false, true),
|
||||||
|
Scrollable = true,
|
||||||
|
ScrollSpeed = 0.5f,
|
||||||
|
Style = CUIStylePrefab.Main,
|
||||||
|
};
|
||||||
|
|
||||||
|
this["layout"]["main"]["msg"] = new CUITextBlock(msg)
|
||||||
|
{
|
||||||
|
TextScale = 1.2f,
|
||||||
|
Wrap = true,
|
||||||
|
Font = GUIStyle.Font,
|
||||||
|
TextAlign = CUIAnchor.TopCenter,
|
||||||
|
Style = new CUIStyle()
|
||||||
|
{
|
||||||
|
["Padding"] = "[10,10]",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this["layout"]["ok"] = new CUIButton("Ok")
|
||||||
|
{
|
||||||
|
TextScale = 1.0f,
|
||||||
|
Style = new CUIStyle()
|
||||||
|
{
|
||||||
|
["Padding"] = "[10,10]",
|
||||||
|
},
|
||||||
|
AddOnMouseDown = (e) => this.RemoveSelf(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
// hmm, idk if this should be a prefab or component
|
||||||
|
// it's too small for component
|
||||||
|
// but in prefab i can't use initializer
|
||||||
|
public class CUICloseButton : CUIButton
|
||||||
|
{
|
||||||
|
public CUICloseButton() : base()
|
||||||
|
{
|
||||||
|
Command = "Close";
|
||||||
|
Text = "";
|
||||||
|
ZIndex = 10;
|
||||||
|
BackgroundSprite = CUI.TextureManager.GetCUISprite(3, 1);
|
||||||
|
Absolute = new CUINullRect(0, 0, 15, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// It's a debug tool, you can use it with cuimg command, it's very fps comsuming
|
||||||
|
/// </summary>
|
||||||
|
[NoDefault]
|
||||||
|
public class CUIMagnifyingGlass : CUICanvas
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public static CUIFrame GlassFrame;
|
||||||
|
|
||||||
|
public static void AddToggleButton()
|
||||||
|
{
|
||||||
|
CUI.TopMain["ToggleMagnifyingGlass"] = new CUIButton("MG")
|
||||||
|
{
|
||||||
|
Absolute = new CUINullRect(0, 0, 20, 20),
|
||||||
|
Anchor = CUIAnchor.CenterLeft,
|
||||||
|
AddOnMouseDown = (e) => ToggleEquip(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ToggleEquip()
|
||||||
|
{
|
||||||
|
if (GlassFrame != null)
|
||||||
|
{
|
||||||
|
GlassFrame.RemoveSelf();
|
||||||
|
GlassFrame = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GlassFrame = new CUIFrame()
|
||||||
|
{
|
||||||
|
ZIndex = 100000,
|
||||||
|
|
||||||
|
BackgroundColor = Color.Transparent,
|
||||||
|
Border = new CUIBorder(Color.Cyan, 5),
|
||||||
|
Anchor = CUIAnchor.Center,
|
||||||
|
Absolute = new CUINullRect(w: 200, h: 200),
|
||||||
|
};
|
||||||
|
GlassFrame["glass"] = new CUIMagnifyingGlass();
|
||||||
|
CUI.TopMain["MagnifyingGlass"] = GlassFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CleanUp()
|
||||||
|
{
|
||||||
|
texture.Dispose();
|
||||||
|
base.CleanUp();
|
||||||
|
}
|
||||||
|
Texture2D texture;
|
||||||
|
Color[] backBuffer;
|
||||||
|
|
||||||
|
|
||||||
|
double lastDrawn;
|
||||||
|
public override void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (Timing.TotalTime - lastDrawn > 0.05)
|
||||||
|
{
|
||||||
|
lastDrawn = Timing.TotalTime;
|
||||||
|
|
||||||
|
GameMain.Instance.GraphicsDevice.GetBackBufferData<Color>(backBuffer);
|
||||||
|
texture.SetData(backBuffer);
|
||||||
|
|
||||||
|
texture.GetData<Color>(
|
||||||
|
0, new Rectangle((int)Real.Left, (int)Real.Top, 40, 40), Data, 0, Data.Length
|
||||||
|
);
|
||||||
|
SetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
base.Draw(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIMagnifyingGlass() : base()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
Size = new Point(40, 40);
|
||||||
|
SamplerState = CUI.NoSmoothing;
|
||||||
|
Relative = new CUINullRect(0, 0, 1, 1);
|
||||||
|
|
||||||
|
int w = GameMain.Instance.GraphicsDevice.PresentationParameters.BackBufferWidth;
|
||||||
|
int h = GameMain.Instance.GraphicsDevice.PresentationParameters.BackBufferHeight;
|
||||||
|
|
||||||
|
backBuffer = new Color[w * h];
|
||||||
|
|
||||||
|
texture = new Texture2D(GameMain.Instance.GraphicsDevice, w, h, false, GameMain.Instance.GraphicsDevice.PresentationParameters.BackBufferFormat);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using EventInput;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
//TODO move all this to defauld styles
|
||||||
|
/// <summary>
|
||||||
|
/// CUITextBlock adapted for CUIMenu
|
||||||
|
/// </summary>
|
||||||
|
public class CUIMenuText : CUITextBlock
|
||||||
|
{
|
||||||
|
public CUIMenuText(string text) : this() => Text = text;
|
||||||
|
public CUIMenuText() : base()
|
||||||
|
{
|
||||||
|
Anchor = CUIAnchor.Center;
|
||||||
|
TextScale = 1.0f;
|
||||||
|
ZIndex = 100;
|
||||||
|
TextColor = Color.Black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component with a sprite that will notify parent CUIMenu when clicked
|
||||||
|
/// </summary>
|
||||||
|
public class CUIMenuOption : CUIComponent
|
||||||
|
{
|
||||||
|
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
|
||||||
|
/// <summary>
|
||||||
|
/// This is the Value that will be send to CUIMenu on click, and will be passed to OnSelect event
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normal background color
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public Color BaseColor
|
||||||
|
{
|
||||||
|
get => (Color)Animations["hover"].StartValue;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Animations["hover"].StartValue = value;
|
||||||
|
Animations["hover"].ApplyValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Background color when hovered
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public Color HoverColor
|
||||||
|
{
|
||||||
|
get => (Color)Animations["hover"].EndValue;
|
||||||
|
set => Animations["hover"].EndValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CUIMenuOption()
|
||||||
|
{
|
||||||
|
BackgroundColor = new Color(255, 255, 255, 255);
|
||||||
|
Relative = new CUINullRect(0, 0, 1, 1);
|
||||||
|
|
||||||
|
IgnoreTransparent = true;
|
||||||
|
Command = "CUIMenuOption select";
|
||||||
|
OnMouseDown += (e) =>
|
||||||
|
{
|
||||||
|
SoundPlayer.PlayUISound(ClickSound);
|
||||||
|
DispatchUp(new CUICommand(Command, Value));
|
||||||
|
};
|
||||||
|
|
||||||
|
Animations["hover"] = new CUIAnimation()
|
||||||
|
{
|
||||||
|
StartValue = new Color(255, 255, 255, 255),
|
||||||
|
EndValue = new Color(255, 255, 255, 255),
|
||||||
|
Duration = 0.1,
|
||||||
|
ReverseDuration = 0.3,
|
||||||
|
Property = "BackgroundColor",
|
||||||
|
};
|
||||||
|
|
||||||
|
OnMouseEnter += (e) => Animations["hover"].Forward();
|
||||||
|
OnMouseLeave += (e) => Animations["hover"].Back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CUIMenu : CUIComponent, IKeyboardSubscriber
|
||||||
|
{
|
||||||
|
// this allows it to intercept esc key press naturally,
|
||||||
|
// but it also blocks normal hotkey bindings, so idk
|
||||||
|
// ----------------- IKeyboardSubscriber -----------------
|
||||||
|
public void ReceiveSpecialInput(Keys key) { if (key == Keys.Escape) Close(); }
|
||||||
|
public void ReceiveTextInput(char inputChar) => ReceiveTextInput(inputChar.ToString());
|
||||||
|
public void ReceiveTextInput(string text) { }
|
||||||
|
public void ReceiveCommandInput(char command) { }
|
||||||
|
public void ReceiveEditingInput(string text, int start, int length) { }
|
||||||
|
public bool Selected { get; set; }
|
||||||
|
// ----------------- IKeyboardSubscriber -----------------
|
||||||
|
|
||||||
|
public static void InitStatic() => CUI.OnDispose += () => Menus.Clear();
|
||||||
|
/// <summary>
|
||||||
|
/// All loaded menus are stored here by Name
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, CUIMenu> Menus = new();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initial fade in animation duration, set to 0 to disable
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable]
|
||||||
|
public double FadeInDuration
|
||||||
|
{
|
||||||
|
get => Animations["fade"].Duration;
|
||||||
|
set => Animations["fade"].Duration = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will be used as key for this menu in CUIMenu.Menus
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string Name { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Does nothing, just a prop so you could get author programmatically
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public string Author { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true will act as IKeyboardSubscriber. Don't
|
||||||
|
/// </summary>
|
||||||
|
[CUISerializable] public bool BlockInput { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Happens when some CUIMenuOption is clicked, the value of that option is passed to it
|
||||||
|
/// </summary>
|
||||||
|
public event Action<string> OnSelect;
|
||||||
|
/// <summary>
|
||||||
|
/// Will add this as a child to host (CUI.Main) and start fadein animation
|
||||||
|
/// </summary>
|
||||||
|
public void Open(CUIComponent host = null)
|
||||||
|
{
|
||||||
|
if (Parent != null) return;
|
||||||
|
host ??= CUI.Main;
|
||||||
|
host.Append(this);
|
||||||
|
|
||||||
|
if (BlockInput) CUI.FocusedComponent = this;
|
||||||
|
|
||||||
|
Animations["fade"].SetToStart();
|
||||||
|
Animations["fade"].Forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close() => RemoveSelf();
|
||||||
|
|
||||||
|
public void Toggle(CUIComponent host = null)
|
||||||
|
{
|
||||||
|
if (Parent != null) Close();
|
||||||
|
else Open(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads CUIMenu from a file to CUIMenu.Menus
|
||||||
|
/// </summary>
|
||||||
|
public static CUIMenu Load(string path)
|
||||||
|
{
|
||||||
|
CUIMenu menu = CUIComponent.LoadFromFile<CUIMenu>(path);
|
||||||
|
if (menu == null) CUI.Warning($"Couldn't load CUIMenu from {path}");
|
||||||
|
if (menu?.Name != null) Menus[menu.Name] = menu;
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIMenu() : base()
|
||||||
|
{
|
||||||
|
BackgroundColor = new Color(255, 255, 255, 255);
|
||||||
|
Anchor = CUIAnchor.Center;
|
||||||
|
Transparency = 0.0f;
|
||||||
|
|
||||||
|
AddCommand("CUIMenuOption select", (o) =>
|
||||||
|
{
|
||||||
|
if (o is string s) OnSelect?.Invoke(s);
|
||||||
|
//Close();
|
||||||
|
});
|
||||||
|
|
||||||
|
Animations["fade"] = new CUIAnimation()
|
||||||
|
{
|
||||||
|
StartValue = 0.0f,
|
||||||
|
EndValue = 1.0f,
|
||||||
|
Duration = 0.2,
|
||||||
|
Property = "Transparency",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (CUI.Main != null)
|
||||||
|
{
|
||||||
|
CUI.Main.Global.OnKeyDown += (e) =>
|
||||||
|
{
|
||||||
|
if (e.PressedKeys.Contains(Keys.Escape)) Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
CUI.Main.OnMouseDown += (e) => Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unfinished crap, don't use
|
||||||
|
/// </summary>
|
||||||
|
public class CUIRadialMenuOption : CUIComponent
|
||||||
|
{
|
||||||
|
public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
|
||||||
|
|
||||||
|
public Color BaseColor
|
||||||
|
{
|
||||||
|
get => (Color)this.Animations["hover"].StartValue;
|
||||||
|
set => this.Animations["hover"].StartValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color Hover
|
||||||
|
{
|
||||||
|
get => (Color)this.Animations["hover"].EndValue;
|
||||||
|
set => this.Animations["hover"].EndValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float TextRadius { get; set; } = 0.4f;
|
||||||
|
|
||||||
|
public void SetRotation(float angle)
|
||||||
|
{
|
||||||
|
BackgroundSprite.Offset = new Vector2(0.5f, 0.5f);
|
||||||
|
BackgroundSprite.Rotation = angle;
|
||||||
|
|
||||||
|
|
||||||
|
this["Text"].Relative = new CUINullRect(
|
||||||
|
(float)(TextRadius * Math.Cos(angle - Math.PI / 2)),
|
||||||
|
(float)(TextRadius * Math.Sin(angle - Math.PI / 2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Callback;
|
||||||
|
|
||||||
|
public CUIRadialMenuOption(string name = "", Action callback = null)
|
||||||
|
{
|
||||||
|
IgnoreTransparent = true;
|
||||||
|
Relative = new CUINullRect(0, 0, 1, 1);
|
||||||
|
|
||||||
|
Callback = callback;
|
||||||
|
OnMouseDown += (e) =>
|
||||||
|
{
|
||||||
|
SoundPlayer.PlayUISound(ClickSound);
|
||||||
|
Callback?.Invoke();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Animations["hover"] = new CUIAnimation()
|
||||||
|
{
|
||||||
|
StartValue = new Color(255, 255, 255, 255),
|
||||||
|
EndValue = new Color(0, 255, 255, 255),
|
||||||
|
Property = "BackgroundColor",
|
||||||
|
Duration = 0.2,
|
||||||
|
ReverseDuration = 0.3,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Animations["hover"].ApplyValue();
|
||||||
|
|
||||||
|
OnMouseEnter += (e) => Animations["hover"].Forward();
|
||||||
|
OnMouseLeave += (e) => Animations["hover"].Back();
|
||||||
|
|
||||||
|
this["Text"] = new CUITextBlock(name)
|
||||||
|
{
|
||||||
|
Anchor = CUIAnchor.Center,
|
||||||
|
ZIndex = 100,
|
||||||
|
TextScale = 1.0f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unfinished crap, don't use
|
||||||
|
/// </summary>
|
||||||
|
public class CUIRadialMenu : CUIComponent
|
||||||
|
{
|
||||||
|
public CUIRadialMenuOption OptionTemplate = new();
|
||||||
|
|
||||||
|
public Dictionary<string, CUIRadialMenuOption> Options = new();
|
||||||
|
|
||||||
|
public CUIRadialMenuOption AddOption(string name, Action callback)
|
||||||
|
{
|
||||||
|
CUIRadialMenuOption option = new CUIRadialMenuOption(name, callback);
|
||||||
|
option.ApplyState(OptionTemplate);
|
||||||
|
option.Animations["hover"].Interpolate = OptionTemplate.Animations["hover"].Interpolate;
|
||||||
|
option.Animations["hover"].ApplyValue();
|
||||||
|
|
||||||
|
this[name] = Options[name] = option;
|
||||||
|
option.OnClick += (e) => Close();
|
||||||
|
|
||||||
|
CalculateRotations();
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CalculateRotations()
|
||||||
|
{
|
||||||
|
float delta = (float)(Math.PI * 2 / Options.Count);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (CUIRadialMenuOption option in Options.Values)
|
||||||
|
{
|
||||||
|
option.SetRotation(delta * i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOpened => Parent != null;
|
||||||
|
|
||||||
|
public void Open(CUIComponent host = null)
|
||||||
|
{
|
||||||
|
host ??= CUI.Main;
|
||||||
|
host.Append(this);
|
||||||
|
Animations["fade"].SetToStart();
|
||||||
|
Animations["fade"].Forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
// BlockChildrenAnimations();
|
||||||
|
// Animations["fade"].SetToEnd();
|
||||||
|
// Animations["fade"].Back();
|
||||||
|
RemoveSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CUIRadialMenu() : base()
|
||||||
|
{
|
||||||
|
Anchor = CUIAnchor.Center;
|
||||||
|
Relative = new CUINullRect(h: 0.8f);
|
||||||
|
CrossRelative = new CUINullRect(w: 0.8f);
|
||||||
|
BackgroundColor = new Color(255, 255, 255, 255);
|
||||||
|
//BackgroundSprite = new CUISprite("RadialMenu.png");
|
||||||
|
|
||||||
|
Animations["fade"] = new CUIAnimation()
|
||||||
|
{
|
||||||
|
StartValue = 0.0f,
|
||||||
|
EndValue = 1.0f,
|
||||||
|
Property = "Transparency",
|
||||||
|
Duration = 0.2,
|
||||||
|
ReverseDuration = 0.1,
|
||||||
|
};
|
||||||
|
|
||||||
|
//HACK
|
||||||
|
Animations["fade"].OnStop += (dir) =>
|
||||||
|
{
|
||||||
|
if (dir == CUIDirection.Reverse)
|
||||||
|
{
|
||||||
|
RemoveSelf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Horizontal range input
|
||||||
|
/// </summary>
|
||||||
|
public class CUISlider : CUIComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Happens when handle is dragged, value is [0..1]
|
||||||
|
/// </summary>
|
||||||
|
public event Action<float> OnSlide;
|
||||||
|
public Action<float> AddOnSlide { set { OnSlide += value; } }
|
||||||
|
public float InOutMult => (Real.Width - Real.Height) / Real.Width;
|
||||||
|
|
||||||
|
private float lambda;
|
||||||
|
private float? pendingLambda;
|
||||||
|
public float Lambda
|
||||||
|
{
|
||||||
|
get => lambda;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lambda = Math.Clamp(value, 0, 1);
|
||||||
|
pendingLambda = lambda;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CUISerializable] public FloatRange Range { get; set; } = new FloatRange(0, 1);
|
||||||
|
[CUISerializable] public int? Precision { get; set; } = 2;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The handle
|
||||||
|
/// </summary>
|
||||||
|
public CUIComponent Handle;
|
||||||
|
|
||||||
|
public CUIComponent LeftEnding;
|
||||||
|
public CUIComponent RightEnding;
|
||||||
|
public CUISprite MiddleSprite;
|
||||||
|
|
||||||
|
public CUIRect MiddleRect;
|
||||||
|
|
||||||
|
public Color MasterColor
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (LeftEnding != null) LeftEnding.BackgroundColor = value;
|
||||||
|
if (RightEnding != null) RightEnding.BackgroundColor = value;
|
||||||
|
if (Handle != null) Handle.BackgroundColor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
base.Draw(spriteBatch);
|
||||||
|
CUI.DrawRectangle(spriteBatch, MiddleRect, LeftEnding.BackgroundColor, MiddleSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUISlider() : base()
|
||||||
|
{
|
||||||
|
ChildrenBoundaries = CUIBoundaries.Box;
|
||||||
|
BreakSerialization = true;
|
||||||
|
|
||||||
|
this["LeftEnding"] = LeftEnding = new CUIComponent()
|
||||||
|
{
|
||||||
|
Anchor = CUIAnchor.CenterLeft,
|
||||||
|
Relative = new CUINullRect(h: 1),
|
||||||
|
CrossRelative = new CUINullRect(w: 1),
|
||||||
|
BackgroundSprite = CUI.TextureManager.GetCUISprite(2, 2, CUISpriteDrawMode.Resize, SpriteEffects.FlipHorizontally),
|
||||||
|
Style = new CUIStyle()
|
||||||
|
{
|
||||||
|
["Border"] = "Transparent",
|
||||||
|
["BackgroundColor"] = "CUIPalette.Slider",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this["RightEnding"] = RightEnding = new CUIComponent()
|
||||||
|
{
|
||||||
|
Anchor = CUIAnchor.CenterRight,
|
||||||
|
Relative = new CUINullRect(h: 1),
|
||||||
|
CrossRelative = new CUINullRect(w: 1),
|
||||||
|
BackgroundSprite = CUI.TextureManager.GetCUISprite(2, 2),
|
||||||
|
Style = new CUIStyle()
|
||||||
|
{
|
||||||
|
["Border"] = "Transparent",
|
||||||
|
["BackgroundColor"] = "CUIPalette.Slider",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this["handle"] = Handle = new CUIComponent()
|
||||||
|
{
|
||||||
|
Style = new CUIStyle()
|
||||||
|
{
|
||||||
|
["Border"] = "Transparent",
|
||||||
|
["BackgroundColor"] = "CUIPalette.Slider",
|
||||||
|
},
|
||||||
|
Draggable = true,
|
||||||
|
BackgroundSprite = CUI.TextureManager.GetCUISprite(0, 2),
|
||||||
|
Relative = new CUINullRect(h: 1),
|
||||||
|
CrossRelative = new CUINullRect(w: 1),
|
||||||
|
AddOnDrag = (x, y) =>
|
||||||
|
{
|
||||||
|
lambda = Math.Clamp(x / InOutMult, 0, 1);
|
||||||
|
OnSlide?.Invoke(lambda);
|
||||||
|
if (Command != null)
|
||||||
|
{
|
||||||
|
float value = Range.PosOf(lambda);
|
||||||
|
if (Precision.HasValue) value = (float)Math.Round(value, Precision.Value);
|
||||||
|
DispatchUp(new CUICommand(Command, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Handle.DragHandle.DragRelative = true;
|
||||||
|
|
||||||
|
MiddleSprite = CUI.TextureManager.GetSprite("CUI.png", new Rectangle(44, 64, 6, 32));
|
||||||
|
|
||||||
|
OnLayoutUpdated += () =>
|
||||||
|
{
|
||||||
|
MiddleRect = new CUIRect(
|
||||||
|
Real.Left + Real.Height,
|
||||||
|
Real.Top,
|
||||||
|
Real.Width - 2 * Real.Height,
|
||||||
|
Real.Height
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pendingLambda.HasValue)
|
||||||
|
{
|
||||||
|
Handle.Relative = Handle.Relative with
|
||||||
|
{
|
||||||
|
Left = Math.Clamp(pendingLambda.Value, 0, 1) * InOutMult,
|
||||||
|
};
|
||||||
|
pendingLambda = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
OnConsume += (o) =>
|
||||||
|
{
|
||||||
|
if (float.TryParse(o.ToString(), out float value))
|
||||||
|
{
|
||||||
|
Lambda = Range.LambdaOf(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Just a tick box
|
||||||
|
/// </summary>
|
||||||
|
public class CUITickBox : CUIComponent
|
||||||
|
{
|
||||||
|
public GUISoundType ClickSound { get; set; } = GUISoundType.TickBox;
|
||||||
|
|
||||||
|
public event Action<bool> OnStateChange;
|
||||||
|
public void AddOnStateChange(Action<bool> callback) => OnStateChange += callback;
|
||||||
|
|
||||||
|
public CUISprite OnSprite { get; set; }
|
||||||
|
public CUISprite OffSprite { get; set; }
|
||||||
|
public CUISprite HoverOffSprite { get; set; }
|
||||||
|
public CUISprite HoverOnSprite { get; set; }
|
||||||
|
public CUISprite DisabledSprite { get; set; }
|
||||||
|
|
||||||
|
private bool state; public bool State
|
||||||
|
{
|
||||||
|
get => state;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (state == value) return;
|
||||||
|
state = value;
|
||||||
|
ChangeSprite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Disabled
|
||||||
|
{
|
||||||
|
get => disabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
disabled = value;
|
||||||
|
ChangeSprite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ChangeSprite()
|
||||||
|
{
|
||||||
|
if (Disabled)
|
||||||
|
{
|
||||||
|
BackgroundSprite = DisabledSprite;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (State)
|
||||||
|
{
|
||||||
|
BackgroundSprite = OnSprite;
|
||||||
|
if (MouseOver) BackgroundSprite = HoverOnSprite;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BackgroundSprite = OffSprite;
|
||||||
|
if (MouseOver) BackgroundSprite = HoverOffSprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CUITickBox() : base()
|
||||||
|
{
|
||||||
|
Absolute = new CUINullRect(w: 20, h: 20);
|
||||||
|
BackgroundColor = Color.Cyan;
|
||||||
|
Border.Color = Color.Transparent;
|
||||||
|
ConsumeMouseClicks = true;
|
||||||
|
ConsumeDragAndDrop = true;
|
||||||
|
ConsumeSwipe = true;
|
||||||
|
|
||||||
|
|
||||||
|
OffSprite = new CUISprite(CUI.CUITexturePath)
|
||||||
|
{
|
||||||
|
SourceRect = new Rectangle(0, 0, 32, 32),
|
||||||
|
};
|
||||||
|
|
||||||
|
OnSprite = new CUISprite(CUI.CUITexturePath)
|
||||||
|
{
|
||||||
|
SourceRect = new Rectangle(32, 0, 32, 32),
|
||||||
|
};
|
||||||
|
|
||||||
|
HoverOffSprite = new CUISprite(CUI.CUITexturePath)
|
||||||
|
{
|
||||||
|
SourceRect = new Rectangle(64, 0, 32, 32),
|
||||||
|
};
|
||||||
|
HoverOnSprite = new CUISprite(CUI.CUITexturePath)
|
||||||
|
{
|
||||||
|
SourceRect = new Rectangle(96, 0, 32, 32),
|
||||||
|
};
|
||||||
|
|
||||||
|
DisabledSprite = new CUISprite(CUI.CUITexturePath)
|
||||||
|
{
|
||||||
|
SourceRect = new Rectangle(128, 0, 32, 32),
|
||||||
|
};
|
||||||
|
|
||||||
|
ChangeSprite();
|
||||||
|
|
||||||
|
OnMouseDown += (e) =>
|
||||||
|
{
|
||||||
|
if (Disabled) return;
|
||||||
|
|
||||||
|
SoundPlayer.PlayUISound(ClickSound);
|
||||||
|
State = !State;
|
||||||
|
OnStateChange?.Invoke(State);
|
||||||
|
if (Command != null) DispatchUp(new CUICommand(Command, State));
|
||||||
|
};
|
||||||
|
|
||||||
|
OnMouseEnter += (e) => ChangeSprite();
|
||||||
|
OnMouseLeave += (e) => ChangeSprite();
|
||||||
|
|
||||||
|
OnConsume += (o) =>
|
||||||
|
{
|
||||||
|
if (o is bool b) State = b;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Just an example of what CUICanvas can be used for
|
||||||
|
/// </summary>
|
||||||
|
public class CUIWater : CUICanvas
|
||||||
|
{
|
||||||
|
public float Omega = 1.999f;
|
||||||
|
|
||||||
|
public float[,] Pool1;
|
||||||
|
public float[,] Pool2;
|
||||||
|
public float[,] DensityMap;
|
||||||
|
|
||||||
|
public Color[] ColorPalette = new Color[]{
|
||||||
|
new Color(0,0,0,0),
|
||||||
|
new Color(0,0,64),
|
||||||
|
new Color(32,0,64),
|
||||||
|
new Color(255,0,255),
|
||||||
|
new Color(0,255,255),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public override Point Size
|
||||||
|
{
|
||||||
|
get => base.Size;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Size = value;
|
||||||
|
Pool1 = new float[Texture.Width, Texture.Height];
|
||||||
|
Pool2 = new float[Texture.Width, Texture.Height];
|
||||||
|
DensityMap = new float[Texture.Width, Texture.Height];
|
||||||
|
RandomizeDensityMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float NextAmplitude(int x, int y)
|
||||||
|
{
|
||||||
|
float avg = (
|
||||||
|
Pool1[x + 1, y] +
|
||||||
|
Pool1[x, y + 1] +
|
||||||
|
Pool1[x - 1, y] +
|
||||||
|
Pool1[x, y - 1]
|
||||||
|
) / 4.0f;
|
||||||
|
|
||||||
|
return avg * Omega + (1 - Omega) * Pool2[x, y];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Step()
|
||||||
|
{
|
||||||
|
for (int x = 1; x < Size.X - 1; x++)
|
||||||
|
{
|
||||||
|
for (int y = 1; y < Size.Y - 1; y++)
|
||||||
|
{
|
||||||
|
Pool2[x, y] = NextAmplitude(x, y) * DensityMap[x, y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(Pool1, Pool2) = (Pool2, Pool1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double UpdateInterval = 1.0 / 60.0;
|
||||||
|
private double lastUpdateTime = -1;
|
||||||
|
public void Update(double totalTime)
|
||||||
|
{
|
||||||
|
if (totalTime - lastUpdateTime < UpdateInterval) return;
|
||||||
|
UpdateSelf();
|
||||||
|
Step();
|
||||||
|
lastUpdateTime = totalTime;
|
||||||
|
|
||||||
|
TransferData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void UpdateSelf()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TransferData()
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Size.X; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < Size.Y; y++)
|
||||||
|
{
|
||||||
|
SetPixel(x, y, ToolBox.GradientLerp(Math.Abs(Pool1[x, y]), ColorPalette));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RandomizeDensityMap()
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Size.X; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < Size.Y; y++)
|
||||||
|
{
|
||||||
|
DensityMap[x, y] = 1.0f - CUI.Random.NextSingle() * 0.01f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public float DropSize = 16.0f;
|
||||||
|
public void Drop(float x, float y)
|
||||||
|
{
|
||||||
|
int i = (int)Math.Clamp(Math.Round(x * Texture.Width), 1, Texture.Width - 2);
|
||||||
|
int j = (int)Math.Clamp(Math.Round(y * Texture.Height), 1, Texture.Height - 2);
|
||||||
|
|
||||||
|
Pool1[i, j] = DropSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public CUIWater(int x, int y) : base(x, y)
|
||||||
|
{
|
||||||
|
//ConsumeDragAndDrop = true;
|
||||||
|
|
||||||
|
//OnUpdate += Update;
|
||||||
|
Pool1 = new float[Texture.Width, Texture.Height];
|
||||||
|
Pool2 = new float[Texture.Width, Texture.Height];
|
||||||
|
DensityMap = new float[Texture.Width, Texture.Height];
|
||||||
|
RandomizeDensityMap();
|
||||||
|
|
||||||
|
// OnMouseOn += (e) =>
|
||||||
|
// {
|
||||||
|
// if (!MousePressed) return;
|
||||||
|
// Vector2 v = CUIAnchor.AnchorFromPos(Real, e.MousePosition);
|
||||||
|
// Drop(v.X, v.Y);
|
||||||
|
// };
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIWater() : this(256, 256)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebug.cs
Normal file
95
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebug.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebugWindow.cs
Normal file
223
Quick Interactions/CSharp/Client/CrabUI/Debug/CUIDebugWindow.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public class CUIDragHandle : ICUIVitalizable
|
||||||
|
{
|
||||||
|
public void SetHost(CUIComponent host) => Host = host;
|
||||||
|
public CUIComponent Host;
|
||||||
|
public Vector2 GrabOffset;
|
||||||
|
public bool Grabbed;
|
||||||
|
public bool Draggable;
|
||||||
|
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||||
|
/// <summary>
|
||||||
|
/// If true, will change relative prop instead of Absolute
|
||||||
|
/// </summary>
|
||||||
|
public bool DragRelative { get; set; } = false;
|
||||||
|
public bool OutputRealPos { get; set; } = false;
|
||||||
|
|
||||||
|
public bool ShouldStart(CUIInput input)
|
||||||
|
{
|
||||||
|
return Draggable && (
|
||||||
|
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||||
|
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public void BeginDrag(Vector2 cursorPos)
|
||||||
|
{
|
||||||
|
Grabbed = true;
|
||||||
|
GrabOffset = cursorPos - CUIAnchor.PosIn(Host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndDrag()
|
||||||
|
{
|
||||||
|
Grabbed = false;
|
||||||
|
Host.MainComponent?.OnDragEnd(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO test in 3d child offset
|
||||||
|
public void DragTo(Vector2 to)
|
||||||
|
{
|
||||||
|
Vector2 pos = Host.Parent.ChildrenOffset.ToPlaneCoords(
|
||||||
|
to - GrabOffset - CUIAnchor.PosIn(Host.Parent.Real, Host.ParentAnchor ?? Host.Anchor)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (DragRelative)
|
||||||
|
{
|
||||||
|
Vector2 newRelPos = new Vector2(
|
||||||
|
pos.X / Host.Parent.Real.Width,
|
||||||
|
pos.Y / Host.Parent.Real.Height
|
||||||
|
);
|
||||||
|
Host.CUIProps.Relative.SetValue(Host.Relative with { Position = newRelPos });
|
||||||
|
Host.InvokeOnDrag(newRelPos.X, newRelPos.Y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Host.CUIProps.Absolute.SetValue(Host.Absolute with { Position = pos });
|
||||||
|
if (OutputRealPos) Host.InvokeOnDrag(to.X, to.Y);
|
||||||
|
else Host.InvokeOnDrag(pos.X, pos.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIDragHandle() { }
|
||||||
|
public CUIDragHandle(CUIComponent host) => Host = host;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public class CUIFocusHandle : ICUIVitalizable
|
||||||
|
{
|
||||||
|
public void SetHost(CUIComponent host) => Host = host;
|
||||||
|
public CUIComponent Host;
|
||||||
|
public bool Focusable;
|
||||||
|
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||||
|
|
||||||
|
public bool ShouldStart(CUIInput input)
|
||||||
|
{
|
||||||
|
return Focusable && (
|
||||||
|
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||||
|
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIFocusHandle() { }
|
||||||
|
public CUIFocusHandle(CUIComponent host) => Host = host;
|
||||||
|
}
|
||||||
|
}
|
||||||
137
Quick Interactions/CSharp/Client/CrabUI/Events/CUIInput.cs
Normal file
137
Quick Interactions/CSharp/Client/CrabUI/Events/CUIInput.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public class CUIResizeHandle : ICUIVitalizable
|
||||||
|
{
|
||||||
|
public void SetHost(CUIComponent host) => Host = host;
|
||||||
|
public CUIComponent Host;
|
||||||
|
public CUIRect Real;
|
||||||
|
|
||||||
|
public Vector2 Anchor;
|
||||||
|
public Vector2 StaticPointAnchor;
|
||||||
|
public Vector2 AnchorDif;
|
||||||
|
|
||||||
|
public CUINullRect Absolute;
|
||||||
|
|
||||||
|
public CUISprite Sprite;
|
||||||
|
public Vector2 MemoStaticPoint;
|
||||||
|
|
||||||
|
public bool Grabbed;
|
||||||
|
public bool Visible = false;
|
||||||
|
|
||||||
|
public CUIBool2 Direction { get; set; } = new CUIBool2(true, true);
|
||||||
|
|
||||||
|
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||||
|
|
||||||
|
public bool ShouldStart(CUIInput input)
|
||||||
|
{
|
||||||
|
return Visible && Real.Contains(input.MousePosition) && (
|
||||||
|
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||||
|
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginResize(Vector2 cursorPos)
|
||||||
|
{
|
||||||
|
Grabbed = true;
|
||||||
|
MemoStaticPoint = CUIAnchor.PosIn(Host.Real, StaticPointAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndResize()
|
||||||
|
{
|
||||||
|
Grabbed = false;
|
||||||
|
Host.MainComponent?.OnResizeEnd(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resize(Vector2 cursorPos)
|
||||||
|
{
|
||||||
|
float limitedX;
|
||||||
|
if (CUIAnchor.Direction(StaticPointAnchor).X >= 0)
|
||||||
|
{
|
||||||
|
limitedX = Math.Max(MemoStaticPoint.X + Real.Width, cursorPos.X);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
limitedX = Math.Min(MemoStaticPoint.X - Real.Width, cursorPos.X);
|
||||||
|
}
|
||||||
|
float limitedY;
|
||||||
|
if (CUIAnchor.Direction(StaticPointAnchor).Y >= 0)
|
||||||
|
{
|
||||||
|
limitedY = Math.Max(MemoStaticPoint.Y + Real.Height, cursorPos.Y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
limitedY = Math.Min(MemoStaticPoint.Y - Real.Height, cursorPos.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 LimitedCursorPos = new Vector2(limitedX, limitedY);
|
||||||
|
|
||||||
|
|
||||||
|
Vector2 RealDif = MemoStaticPoint - LimitedCursorPos;
|
||||||
|
Vector2 SizeFactor = RealDif / AnchorDif;
|
||||||
|
Vector2 TopLeft = MemoStaticPoint - SizeFactor * StaticPointAnchor;
|
||||||
|
|
||||||
|
|
||||||
|
Vector2 newSize = new Vector2(
|
||||||
|
Math.Max(Real.Width, SizeFactor.X),
|
||||||
|
Math.Max(Real.Height, SizeFactor.Y)
|
||||||
|
);
|
||||||
|
|
||||||
|
Vector2 newPos = TopLeft - CUIAnchor.PosIn(Host.Parent.Real, Host.ParentAnchor ?? Host.Anchor) + CUIAnchor.PosIn(new CUIRect(newSize), Host.Anchor);
|
||||||
|
|
||||||
|
if (Direction.X) Host.CUIProps.Absolute.SetValue(new CUINullRect(newPos.X, Host.Absolute.Top, newSize.X, Host.Absolute.Height));
|
||||||
|
if (Direction.Y) Host.CUIProps.Absolute.SetValue(new CUINullRect(Host.Absolute.Left, newPos.Y, Host.Absolute.Width, newSize.Y));
|
||||||
|
}
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (!Visible) return;
|
||||||
|
|
||||||
|
float x, y, w, h;
|
||||||
|
x = y = w = h = 0;
|
||||||
|
|
||||||
|
if (Absolute.Left.HasValue) x = Absolute.Left.Value;
|
||||||
|
if (Absolute.Top.HasValue) y = Absolute.Top.Value;
|
||||||
|
if (Absolute.Width.HasValue) w = Absolute.Width.Value;
|
||||||
|
if (Absolute.Height.HasValue) h = Absolute.Height.Value;
|
||||||
|
|
||||||
|
Vector2 Pos = CUIAnchor.GetChildPos(Host.Real, Anchor, new Vector2(x, y), new Vector2(w, h));
|
||||||
|
|
||||||
|
Real = new CUIRect(Pos, new Vector2(w, h));
|
||||||
|
}
|
||||||
|
public void Draw(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (!Visible) return;
|
||||||
|
CUI.DrawRectangle(spriteBatch, Real, Grabbed ? Host.ResizeHandleGrabbedColor : Host.ResizeHandleColor, Sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIResizeHandle(Vector2 anchor, CUIBool2 flipped)
|
||||||
|
{
|
||||||
|
if (anchor == CUIAnchor.Center)
|
||||||
|
{
|
||||||
|
CUI.Log($"Pls don't use CUIAnchor.Center for CUIResizeHandle, it makes no sense:\nThe StaticPointAnchor is symetric to Anchor and in this edge case == Anchor");
|
||||||
|
}
|
||||||
|
|
||||||
|
Anchor = anchor;
|
||||||
|
StaticPointAnchor = Vector2.One - Anchor;
|
||||||
|
AnchorDif = StaticPointAnchor - Anchor;
|
||||||
|
|
||||||
|
Absolute = new CUINullRect(0, 0, 15, 15);
|
||||||
|
Sprite = CUI.TextureManager.GetSprite(CUI.CUITexturePath);
|
||||||
|
Sprite.SourceRect = new Rectangle(0, 32, 32, 32);
|
||||||
|
if (flipped.X) Sprite.Effects |= SpriteEffects.FlipHorizontally;
|
||||||
|
if (flipped.Y) Sprite.Effects |= SpriteEffects.FlipVertically;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public class CUISwipeHandle : ICUIVitalizable
|
||||||
|
{
|
||||||
|
public void SetHost(CUIComponent host) => Host = host;
|
||||||
|
public CUIComponent Host;
|
||||||
|
public bool Grabbed;
|
||||||
|
public bool Swipeable;
|
||||||
|
public Vector2 PrevPosition;
|
||||||
|
public CUIMouseEvent Trigger = CUIMouseEvent.Down;
|
||||||
|
public bool ShouldStart(CUIInput input)
|
||||||
|
{
|
||||||
|
return Swipeable && (
|
||||||
|
(Trigger == CUIMouseEvent.Down && input.MouseDown) ||
|
||||||
|
(Trigger == CUIMouseEvent.DClick && input.DoubleClick)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginSwipe(Vector2 cursorPos)
|
||||||
|
{
|
||||||
|
Grabbed = true;
|
||||||
|
PrevPosition = cursorPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndSwipe()
|
||||||
|
{
|
||||||
|
Grabbed = false;
|
||||||
|
Host.MainComponent?.OnSwipeEnd(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Swipe(CUIInput input)
|
||||||
|
{
|
||||||
|
Host.CUIProps.ChildrenOffset.SetValue(
|
||||||
|
Host.ChildrenOffset.Shift(
|
||||||
|
input.MousePositionDif.X,
|
||||||
|
input.MousePositionDif.Y
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Host.InvokeOnSwipe(input.MousePositionDif.X, input.MousePositionDif.Y);
|
||||||
|
}
|
||||||
|
public CUISwipeHandle() { }
|
||||||
|
|
||||||
|
public CUISwipeHandle(CUIComponent host) => Host = host;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
|
||||||
|
public class CUIWeakEvent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public partial class CUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// $"‖color:{color}‖{msg}‖end‖"
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="color"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string WrapInColor(object msg, string color)
|
||||||
|
{
|
||||||
|
return $"‖color:{color}‖{msg}‖end‖";
|
||||||
|
}
|
||||||
|
|
||||||
|
//HACK too lazy to make good name
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes the array
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string ArrayToString(IEnumerable<object> array)
|
||||||
|
{
|
||||||
|
return $"[{String.Join(", ", array.Select(o => o.ToString()))}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prints a message to console
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="color"></param>
|
||||||
|
public static void Log(object msg, Color? color = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||||
|
{
|
||||||
|
color ??= Color.Cyan;
|
||||||
|
|
||||||
|
// var fi = new FileInfo(source);
|
||||||
|
// LuaCsLogger.LogMessage($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", color * 0.6f, color * 0.6f);
|
||||||
|
|
||||||
|
LuaCsLogger.LogMessage($"{msg ?? "null"}", color * 0.8f, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warning(object msg, Color? color = null, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||||
|
{
|
||||||
|
color ??= Color.Yellow;
|
||||||
|
// var fi = new FileInfo(source);
|
||||||
|
// LuaCsLogger.LogMessage($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", color * 0.6f, color * 0.6f);
|
||||||
|
LuaCsLogger.LogMessage($"{msg ?? "null"}", color * 0.8f, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// xd
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source"> This should be injected by compiler, don't set </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetCallerFolderPath([CallerFilePath] string source = "") => Path.GetDirectoryName(source);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prints debug message with source path
|
||||||
|
/// Works only if debug is true
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
public static void Info(object msg, [CallerFilePath] string source = "", [CallerLineNumber] int lineNumber = 0)
|
||||||
|
{
|
||||||
|
if (Debug == true)
|
||||||
|
{
|
||||||
|
var fi = new FileInfo(source);
|
||||||
|
|
||||||
|
Log($"{fi.Directory.Name}/{fi.Name}:{lineNumber}", Color.Yellow * 0.5f);
|
||||||
|
Log(msg, Color.Yellow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
218
Quick Interactions/CSharp/Client/CrabUI/Global/CUI Patches.cs
Normal file
218
Quick Interactions/CSharp/Client/CrabUI/Global/CUI Patches.cs
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Quick Interactions/CSharp/Client/CrabUI/Global/CUIBuilder.cs
Normal file
29
Quick Interactions/CSharp/Client/CrabUI/Global/CUIBuilder.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
155
Quick Interactions/CSharp/Client/CrabUI/Global/CUICommands.cs
Normal file
155
Quick Interactions/CSharp/Client/CrabUI/Global/CUICommands.cs
Normal 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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
149
Quick Interactions/CSharp/Client/CrabUI/Global/CUIDrawing.cs
Normal file
149
Quick Interactions/CSharp/Client/CrabUI/Global/CUIDrawing.cs
Normal 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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Quick Interactions/CSharp/Client/CrabUI/Global/CUIErrors.cs
Normal file
27
Quick Interactions/CSharp/Client/CrabUI/Global/CUIErrors.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
220
Quick Interactions/CSharp/Client/CrabUI/Global/CUIExtensions.cs
Normal file
220
Quick Interactions/CSharp/Client/CrabUI/Global/CUIExtensions.cs
Normal 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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.IO;
|
||||||
|
using EventInput;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public class CUIFocusResolver
|
||||||
|
{
|
||||||
|
private CUIComponent focusedCUIComponent;
|
||||||
|
public CUIComponent FocusedCUIComponent
|
||||||
|
{
|
||||||
|
get => focusedCUIComponent;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CUIComponent oldFocused = focusedCUIComponent;
|
||||||
|
CUIComponent newFocused = value;
|
||||||
|
|
||||||
|
if (oldFocused == newFocused) return;
|
||||||
|
|
||||||
|
if (oldFocused != null)
|
||||||
|
{
|
||||||
|
oldFocused.Focused = false;
|
||||||
|
oldFocused.InvokeOnFocusLost();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFocused != null)
|
||||||
|
{
|
||||||
|
newFocused.Focused = true;
|
||||||
|
newFocused.InvokeOnFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldFocused is IKeyboardSubscriber || newFocused is null)
|
||||||
|
{
|
||||||
|
OnVanillaIKeyboardSubscriberSet(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFocused is IKeyboardSubscriber)
|
||||||
|
{
|
||||||
|
OnVanillaIKeyboardSubscriberSet((IKeyboardSubscriber)newFocused, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
focusedCUIComponent = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnVanillaIKeyboardSubscriberSet(IKeyboardSubscriber value, bool callFromCUI = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
KeyboardDispatcher _ = GUI.KeyboardDispatcher;
|
||||||
|
|
||||||
|
IKeyboardSubscriber oldSubscriber = _._subscriber;
|
||||||
|
IKeyboardSubscriber newSubscriber = value;
|
||||||
|
|
||||||
|
if (newSubscriber == oldSubscriber) { return; }
|
||||||
|
|
||||||
|
// this case should be handled in CUI
|
||||||
|
if (!callFromCUI && oldSubscriber is CUIComponent && newSubscriber is null) { return; }
|
||||||
|
|
||||||
|
//CUI.Log($"new IKeyboardSubscriber {oldSubscriber} -> {newSubscriber}");
|
||||||
|
|
||||||
|
if (oldSubscriber != null)
|
||||||
|
{
|
||||||
|
TextInput.StopTextInput();
|
||||||
|
oldSubscriber.Selected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldSubscriber is CUIComponent component && newSubscriber is GUITextBox)
|
||||||
|
{
|
||||||
|
//TODO for some season TextInput doesn't loose focus here
|
||||||
|
component.InvokeOnFocusLost();
|
||||||
|
component.Focused = false;
|
||||||
|
focusedCUIComponent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSubscriber != null)
|
||||||
|
{
|
||||||
|
if (newSubscriber is GUITextBox box)
|
||||||
|
{
|
||||||
|
TextInput.SetTextInputRect(box.MouseRect);
|
||||||
|
TextInput.StartTextInput();
|
||||||
|
TextInput.SetTextInputRect(box.MouseRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSubscriber is CUIComponent)
|
||||||
|
{
|
||||||
|
TextInput.StartTextInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
newSubscriber.Selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_._subscriber = value;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#define USELUA
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using HarmonyLib;
|
||||||
|
using MoonSharp.Interpreter;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public class CUIInternalAttribute : System.Attribute { }
|
||||||
|
[CUIInternal]
|
||||||
|
public class CUILuaRegistrar
|
||||||
|
{
|
||||||
|
public static string CUITypesFile => Path.Combine(CUI.LuaFolder, "CUITypes.lua");
|
||||||
|
|
||||||
|
public static bool IsRealCUIType(Type T)
|
||||||
|
{
|
||||||
|
if (T.DeclaringType != null) return false; // nested type
|
||||||
|
if (T.Name == "<>c") return false; // guh
|
||||||
|
if (T.IsGenericType) return false; // in lua?
|
||||||
|
if (T.IsInterface) return false;
|
||||||
|
if (T.IsSubclassOf(typeof(Attribute))) return false;
|
||||||
|
if (Attribute.IsDefined(T, typeof(CUIInternalAttribute))) return false;
|
||||||
|
if (typeof(CUILuaRegistrar).Namespace != T.Namespace) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if !USELUA
|
||||||
|
[Conditional("DONT")]
|
||||||
|
#endif
|
||||||
|
public void Register()
|
||||||
|
{
|
||||||
|
if (CUI.LuaFolder == null) return;
|
||||||
|
if (!Directory.Exists(CUI.LuaFolder) || !CUI.UseLua) return;
|
||||||
|
|
||||||
|
Assembly thisAssembly = Assembly.GetAssembly(typeof(CUILuaRegistrar));
|
||||||
|
|
||||||
|
foreach (Type T in thisAssembly.GetTypes().Where(IsRealCUIType))
|
||||||
|
{
|
||||||
|
LuaUserData.RegisterType(T.FullName);
|
||||||
|
// This has to be done in lua
|
||||||
|
//GameMain.LuaCs.Lua.Globals[T.Name] = UserData.CreateStatic(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMain.LuaCs.RegisterAction<CUIInput>();
|
||||||
|
GameMain.LuaCs.RegisterAction<float, float>();
|
||||||
|
GameMain.LuaCs.RegisterAction<TextInputEventArgs>();
|
||||||
|
GameMain.LuaCs.RegisterAction<string>();
|
||||||
|
GameMain.LuaCs.RegisterAction<CUIComponent>();
|
||||||
|
GameMain.LuaCs.RegisterAction<bool>();
|
||||||
|
GameMain.LuaCs.RegisterAction<CUIComponent, int>();
|
||||||
|
|
||||||
|
|
||||||
|
LuaUserData.RegisterType(typeof(CUI).FullName);
|
||||||
|
GameMain.LuaCs.Lua.Globals[nameof(CUI)] = UserData.CreateStatic(typeof(CUI));
|
||||||
|
|
||||||
|
ConstructLuaStaticsFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !USELUA
|
||||||
|
[Conditional("DONT")]
|
||||||
|
#endif
|
||||||
|
public void Deregister()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GameMain.LuaCs.Lua.Globals[nameof(CUI)] = null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConstructLuaStaticsFile()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(CUI.LuaFolder)) return;
|
||||||
|
|
||||||
|
Assembly thisAssembly = Assembly.GetAssembly(typeof(CUILuaRegistrar));
|
||||||
|
|
||||||
|
string content = "-- This file is autogenerated\n";
|
||||||
|
|
||||||
|
foreach (Type T in thisAssembly.GetTypes().Where(IsRealCUIType))
|
||||||
|
{
|
||||||
|
content += $"{T.Name} = LuaUserData.CreateStatic('{T.FullName}', true)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
using (StreamWriter writer = new StreamWriter(CUITypesFile, false))
|
||||||
|
{
|
||||||
|
writer.Write(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public class CUIMultiModResolver
|
||||||
|
{
|
||||||
|
internal static void InitStatic()
|
||||||
|
{
|
||||||
|
CUI.OnInit += () =>
|
||||||
|
{
|
||||||
|
//FindOtherInputs();
|
||||||
|
};
|
||||||
|
CUI.OnDispose += () =>
|
||||||
|
{
|
||||||
|
CUIInputs.Clear();
|
||||||
|
CUIs.Clear();
|
||||||
|
MouseInputHandledMethods.Clear();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<object> CUIInputs = new();
|
||||||
|
public static List<object> CUIs = new();
|
||||||
|
public static List<Action<bool>> MouseInputHandledMethods = new();
|
||||||
|
|
||||||
|
public static void MarkOtherInputsAsHandled()
|
||||||
|
{
|
||||||
|
//MouseInputHandledMethods.ForEach(action => action(true));
|
||||||
|
|
||||||
|
foreach (object input in CUIInputs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PropertyInfo setAsHandled = input.GetType().GetProperty("MouseInputHandled");
|
||||||
|
setAsHandled.SetValue(input, true);
|
||||||
|
CUI.Log($"setAsHandled.SetValue(input, true) for {input}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Couldn't find MouseInputHandled in CUIInput in CUI from other mod ({input.GetType()})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FindOtherInputs()
|
||||||
|
{
|
||||||
|
AppDomain currentDomain = AppDomain.CurrentDomain;
|
||||||
|
|
||||||
|
foreach (Assembly asm in currentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (Type T in asm.GetTypes())
|
||||||
|
{
|
||||||
|
if (T.Name == "CUI")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo InstanceField = T.GetField("Instance", BindingFlags.Static | BindingFlags.Public);
|
||||||
|
object CUIInstance = InstanceField.GetValue(null);
|
||||||
|
if (CUIInstance != null && CUIInstance != CUI.Instance)
|
||||||
|
{
|
||||||
|
CUIs.Add(CUIInstance);
|
||||||
|
FieldInfo inputField = T.GetField("input", AccessTools.all);
|
||||||
|
|
||||||
|
object input = inputField.GetValue(CUIInstance);
|
||||||
|
if (input != null) CUIInputs.Add(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Couldn't find CUIInputs in CUI from other mod ({T})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (object input in CUIInputs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PropertyInfo setAsHandled = input.GetType().GetProperty("MouseInputHandled");
|
||||||
|
MouseInputHandledMethods.Add(setAsHandled.SetMethod.CreateDelegate<Action<bool>>(input));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CUI.Warning($"Couldn't find MouseInputHandled in CUIInput in CUI from other mod ({input.GetType()})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
391
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPalette.cs
Normal file
391
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPalette.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
238
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPrefab.cs
Normal file
238
Quick Interactions/CSharp/Client/CrabUI/Global/CUIPrefab.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
174
Quick Interactions/CSharp/Client/CrabUI/Global/CUIReflection.cs
Normal file
174
Quick Interactions/CSharp/Client/CrabUI/Global/CUIReflection.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
49
Quick Interactions/CSharp/Client/CrabUI/Global/ModStorage.cs
Normal file
49
Quick Interactions/CSharp/Client/CrabUI/Global/ModStorage.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
180
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayout.cs
Normal file
180
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayout.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
182
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayoutGrid.cs
Normal file
182
Quick Interactions/CSharp/Client/CrabUI/Layouts/CUILayoutGrid.cs
Normal 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) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
189
Quick Interactions/CSharp/Client/CrabUI/Style/CUIStyle.cs
Normal file
189
Quick Interactions/CSharp/Client/CrabUI/Style/CUIStyle.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
17
Quick Interactions/CSharp/Client/CrabUI/Types/CUI enums.cs
Normal file
17
Quick Interactions/CSharp/Client/CrabUI/Types/CUI enums.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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 enum CUITextAlign { Start, Center, End, }
|
||||||
|
public enum CUISide { Top, Right, Bottom, Left, }
|
||||||
|
public enum CUIDirection { Straight, Reverse, }
|
||||||
|
public enum CUIMouseEvent { Down, DClick }
|
||||||
|
}
|
||||||
115
Quick Interactions/CSharp/Client/CrabUI/Types/CUI3DOffset.cs
Normal file
115
Quick Interactions/CSharp/Client/CrabUI/Types/CUI3DOffset.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
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;
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Offset of child components with X, Y, Z
|
||||||
|
/// </summary>
|
||||||
|
public struct CUI3DOffset
|
||||||
|
{
|
||||||
|
public float X;
|
||||||
|
public float Y;
|
||||||
|
//HACK this is mega cursed, it doens't want to be set to 1 automatically
|
||||||
|
public float Z;
|
||||||
|
|
||||||
|
public Vector2 ToVector2 => new Vector2(X, Y);
|
||||||
|
|
||||||
|
//TODO unhardcode and move to CUIBoundaries
|
||||||
|
public static float MinZ = 1f;
|
||||||
|
|
||||||
|
public CUI3DOffset Shift(Vector2 shift) => Shift(shift.X, shift.Y);
|
||||||
|
public CUI3DOffset Shift(float x = 0, float y = 0)
|
||||||
|
{
|
||||||
|
return new CUI3DOffset(
|
||||||
|
X + x * Z,
|
||||||
|
Y + y * Z,
|
||||||
|
Z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUI3DOffset Zoom(Vector2 staticPoint, float dZ) => Zoom(staticPoint.X, staticPoint.Y, dZ);
|
||||||
|
public CUI3DOffset Zoom(float sx, float sy, float dZ)
|
||||||
|
{
|
||||||
|
float newZ = Math.Max(MinZ, Z + dZ);
|
||||||
|
Vector2 s1 = new Vector2(sx * Z - X, sy * Z - Y);
|
||||||
|
Vector2 s2 = new Vector2(sx * newZ - X, sy * newZ - Y);
|
||||||
|
Vector2 d = s2 - s1;
|
||||||
|
|
||||||
|
return new CUI3DOffset(X + d.X, Y + d.Y, newZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 ToPlaneCoords(Vector2 v)
|
||||||
|
{
|
||||||
|
return new Vector2(v.X * Z - X, v.Y * Z - Y);
|
||||||
|
}
|
||||||
|
public Vector2 TransformPoint(Vector2 point)
|
||||||
|
{
|
||||||
|
return new Vector2((point.X + X) / Z, (point.Y + Y) / Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 TransformSize(Vector2 size)
|
||||||
|
{
|
||||||
|
return new Vector2(size.X / Z, size.Y / Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIRect Transform(CUIRect rect)
|
||||||
|
{
|
||||||
|
return new CUIRect(
|
||||||
|
(rect.Left + X) / Z,
|
||||||
|
(rect.Top + Y) / Z,
|
||||||
|
rect.Width / Z,
|
||||||
|
rect.Height / Z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XGLBRLGRLXBRSLRLGRKK!!!
|
||||||
|
public CUI3DOffset()
|
||||||
|
{
|
||||||
|
X = 0;
|
||||||
|
Y = 0;
|
||||||
|
Z = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUI3DOffset(float x, float y, float z)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"[{X},{Y},{Z}]";
|
||||||
|
|
||||||
|
public static CUI3DOffset Parse(string s)
|
||||||
|
{
|
||||||
|
string content = s.Substring(
|
||||||
|
s.IndexOf('[') + 1,
|
||||||
|
s.IndexOf(']') - s.IndexOf('[') - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
var components = content.Split(',').Select(a => a.Trim());
|
||||||
|
|
||||||
|
string sx = components.ElementAtOrDefault(0);
|
||||||
|
string sy = components.ElementAtOrDefault(1);
|
||||||
|
string sz = components.ElementAtOrDefault(2);
|
||||||
|
|
||||||
|
float x = 0;
|
||||||
|
float y = 0;
|
||||||
|
float z = 0;
|
||||||
|
|
||||||
|
float.TryParse(sx, out x);
|
||||||
|
float.TryParse(sy, out y);
|
||||||
|
float.TryParse(sz, out z);
|
||||||
|
|
||||||
|
return new CUI3DOffset(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
Quick Interactions/CSharp/Client/CrabUI/Types/CUIAnchor.cs
Normal file
58
Quick Interactions/CSharp/Client/CrabUI/Types/CUIAnchor.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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>
|
||||||
|
/// CUIAnchor is just a Vector2
|
||||||
|
/// This is a static class containing some helper methods
|
||||||
|
/// </summary>
|
||||||
|
public class CUIAnchor
|
||||||
|
{
|
||||||
|
public static Vector2 TopLeft = new Vector2(0.0f, 0.0f);
|
||||||
|
public static Vector2 TopCenter = new Vector2(0.5f, 0.0f);
|
||||||
|
public static Vector2 TopRight = new Vector2(1.0f, 0.0f);
|
||||||
|
public static Vector2 CenterLeft = new Vector2(0.0f, 0.5f);
|
||||||
|
public static Vector2 Center = new Vector2(0.5f, 0.5f);
|
||||||
|
public static Vector2 CenterRight = new Vector2(1.0f, 0.5f);
|
||||||
|
public static Vector2 BottomLeft = new Vector2(0.0f, 1.0f);
|
||||||
|
public static Vector2 BottomCenter = new Vector2(0.5f, 1.0f);
|
||||||
|
public static Vector2 BottomRight = new Vector2(1.0f, 1.0f);
|
||||||
|
|
||||||
|
public static Vector2 Direction(Vector2 anchor)
|
||||||
|
{
|
||||||
|
return (Center - anchor) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 PosIn(CUIComponent host) => PosIn(host.Real, host.Anchor);
|
||||||
|
public static Vector2 PosIn(CUIRect rect, Vector2 anchor)
|
||||||
|
{
|
||||||
|
return new Vector2(
|
||||||
|
rect.Left + rect.Width * anchor.X,
|
||||||
|
rect.Top + rect.Height * anchor.Y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 AnchorFromPos(CUIRect rect, Vector2 pos)
|
||||||
|
{
|
||||||
|
return (pos - rect.Position) / rect.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 GetChildPos(CUIRect parent, Vector2 anchor, Vector2 offset, Vector2 childSize)
|
||||||
|
{
|
||||||
|
return PosIn(parent, anchor) + offset - PosIn(new CUIRect(childSize), anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 GetChildPos(CUIRect parent, Vector2 parentAnchor, Vector2 offset, Vector2 childSize, Vector2 anchor)
|
||||||
|
{
|
||||||
|
return PosIn(parent, parentAnchor) + offset - PosIn(new CUIRect(childSize), anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBool2.cs
Normal file
49
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBool2.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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>
|
||||||
|
/// Vector2 but with bools
|
||||||
|
/// </summary>
|
||||||
|
public struct CUIBool2
|
||||||
|
{
|
||||||
|
public bool X;
|
||||||
|
public bool Y;
|
||||||
|
|
||||||
|
public CUIBool2(bool x = false, bool y = false)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"[{X},{Y}]";
|
||||||
|
public static CUIBool2 Parse(string s)
|
||||||
|
{
|
||||||
|
string content = s.Substring(
|
||||||
|
s.IndexOf('[') + 1,
|
||||||
|
s.IndexOf(']') - s.IndexOf('[') - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
var components = content.Split(',').Select(a => a.Trim());
|
||||||
|
|
||||||
|
string sx = components.ElementAtOrDefault(0);
|
||||||
|
string sy = components.ElementAtOrDefault(1);
|
||||||
|
|
||||||
|
bool x = false;
|
||||||
|
bool y = false;
|
||||||
|
|
||||||
|
if (sx != null && sx != "") x = bool.Parse(sx);
|
||||||
|
if (sy != null && sy != "") y = bool.Parse(sy);
|
||||||
|
|
||||||
|
return new CUIBool2(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
108
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBorder.cs
Normal file
108
Quick Interactions/CSharp/Client/CrabUI/Types/CUIBorder.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
//TODO why is this mutable?
|
||||||
|
public class CUIBorder : ICloneable
|
||||||
|
{
|
||||||
|
private Color color; public Color Color
|
||||||
|
{
|
||||||
|
get => color;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
color = value;
|
||||||
|
UpdateVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private float thickness = 1f; public float Thickness
|
||||||
|
{
|
||||||
|
get => thickness;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
thickness = value;
|
||||||
|
UpdateVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Visible { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public void UpdateVisible()
|
||||||
|
{
|
||||||
|
Visible = Thickness != 0f && color != Color.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIBorder() { }
|
||||||
|
public CUIBorder(Color color, float thickness = 1f)
|
||||||
|
{
|
||||||
|
this.color = color;
|
||||||
|
this.thickness = thickness;
|
||||||
|
UpdateVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is CUIBorder border)
|
||||||
|
{
|
||||||
|
if (Color == border.Color && Thickness == border.Thickness) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Clone()
|
||||||
|
{
|
||||||
|
return new CUIBorder(Color, Thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override string ToString() => $"[{CUIExtensions.ColorToString(Color)},{Thickness}]";
|
||||||
|
public static CUIBorder Parse(string raw)
|
||||||
|
{
|
||||||
|
CUIBorder border = new CUIBorder();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string[] sub = raw.Split('[', ']');
|
||||||
|
|
||||||
|
if (sub.Length == 1)
|
||||||
|
{
|
||||||
|
border.Color = CUIExtensions.ParseColor(sub[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub.Length > 1)
|
||||||
|
{
|
||||||
|
string content = raw.Split('[', ']').ElementAtOrDefault(1);
|
||||||
|
|
||||||
|
if (content.Trim() != "")
|
||||||
|
{
|
||||||
|
IEnumerable<string> values = content.Split(',');
|
||||||
|
if (values.ElementAtOrDefault(0) != null)
|
||||||
|
{
|
||||||
|
border.Color = CUIExtensions.ParseColor(values.ElementAtOrDefault(0));
|
||||||
|
}
|
||||||
|
if (values.ElementAtOrDefault(1) != null)
|
||||||
|
{
|
||||||
|
float t = 1f;
|
||||||
|
if (float.TryParse(values.ElementAtOrDefault(1), out t))
|
||||||
|
{
|
||||||
|
border.Thickness = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e) { CUI.Warning($"Couldn't parse CUIBorder [{raw}]:\n{e.Message}"); }
|
||||||
|
|
||||||
|
return border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
//TODO should be 2 different structs i think
|
||||||
|
/// <summary>
|
||||||
|
/// Defining Boundaries, not the same as rect
|
||||||
|
/// containing min/max x, y, z
|
||||||
|
/// </summary>
|
||||||
|
public struct CUIBoundaries
|
||||||
|
{
|
||||||
|
public static Func<CUIRect, CUIBoundaries> Free = (Rect) => new CUIBoundaries(null, null, null, null);
|
||||||
|
public static Func<CUIRect, CUIBoundaries> Box = (Rect) => new CUIBoundaries(0, Rect.Width, 0, Rect.Height);
|
||||||
|
public static Func<CUIRect, CUIBoundaries> HorizontalTube = (Rect) => new CUIBoundaries(null, null, 0, Rect.Height);
|
||||||
|
public static Func<CUIRect, CUIBoundaries> VerticalTube = (Rect) => new CUIBoundaries(0, Rect.Width, null, null);
|
||||||
|
|
||||||
|
|
||||||
|
public float? MinX;
|
||||||
|
public float? MaxX;
|
||||||
|
public float? MinY;
|
||||||
|
public float? MaxY;
|
||||||
|
|
||||||
|
//TODO minZ is hardcoded in CUI3DOffset, untangle this crap
|
||||||
|
// unusable for now
|
||||||
|
public float? MinZ;
|
||||||
|
public float? MaxZ;
|
||||||
|
|
||||||
|
public CUIRect Check(float x, float y, float w, float h)
|
||||||
|
{
|
||||||
|
if (MaxX.HasValue && x + w > MaxX.Value) x = MaxX.Value - w;
|
||||||
|
if (MaxY.HasValue && y + h > MaxY.Value) y = MaxY.Value - h;
|
||||||
|
if (MinX.HasValue && x < MinX.Value) x = MinX.Value;
|
||||||
|
if (MinY.HasValue && y < MinY.Value) y = MinY.Value;
|
||||||
|
|
||||||
|
return new CUIRect(x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CUI3DOffset Check(CUI3DOffset offset) => Check(offset.X, offset.Y, offset.Z);
|
||||||
|
public CUI3DOffset Check(float x, float y, float z)
|
||||||
|
{
|
||||||
|
if (MaxX.HasValue && x > MaxX.Value) x = MaxX.Value;
|
||||||
|
if (MaxY.HasValue && y > MaxY.Value) y = MaxY.Value;
|
||||||
|
if (MaxZ.HasValue && z > MaxZ.Value) z = MaxZ.Value;
|
||||||
|
|
||||||
|
if (MinX.HasValue && x < MinX.Value) x = MinX.Value;
|
||||||
|
if (MinY.HasValue && y < MinY.Value) y = MinY.Value;
|
||||||
|
if (MinZ.HasValue && z < MinZ.Value) z = MinZ.Value;
|
||||||
|
|
||||||
|
return new CUI3DOffset(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
//HACK Why the fuck vars defined in this order?
|
||||||
|
// probably to not confuse them with pos and size
|
||||||
|
public CUIBoundaries(
|
||||||
|
float? minX = null, float? maxX = null,
|
||||||
|
float? minY = null, float? maxY = null,
|
||||||
|
float? minZ = null, float? maxZ = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
MinX = minX;
|
||||||
|
MaxX = maxX;
|
||||||
|
MinY = minY;
|
||||||
|
MaxY = maxY;
|
||||||
|
MinZ = minZ;
|
||||||
|
MaxZ = maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"[{MinX},{MaxX},{MinY},{MaxY},{MinZ},{MaxZ}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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>
|
||||||
|
/// Vector2, but with float?
|
||||||
|
/// </summary>
|
||||||
|
public struct CUINullVector2
|
||||||
|
{
|
||||||
|
public float? X;
|
||||||
|
public float? Y;
|
||||||
|
|
||||||
|
|
||||||
|
public CUINullVector2()
|
||||||
|
{
|
||||||
|
X = null;
|
||||||
|
Y = null;
|
||||||
|
}
|
||||||
|
public CUINullVector2(Vector2 v)
|
||||||
|
{
|
||||||
|
X = v.X;
|
||||||
|
Y = v.Y;
|
||||||
|
}
|
||||||
|
public CUINullVector2(float? x, float? y)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"[{X},{Y}]";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
79
Quick Interactions/CSharp/Client/CrabUI/Types/CUIProp.cs
Normal file
79
Quick Interactions/CSharp/Client/CrabUI/Types/CUIProp.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper object for prop value, setters, side effects, metadata etc.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Type of the prop </typeparam>
|
||||||
|
public class CUIProp<T> : ICUIProp
|
||||||
|
{
|
||||||
|
public void SetHost(CUIComponent host) => Host = host;
|
||||||
|
public void SetName(string name) => Name = name;
|
||||||
|
|
||||||
|
public CUIComponent Host;
|
||||||
|
public string Name = "Unknown";
|
||||||
|
public Action<T, CUIComponent> OnSet;
|
||||||
|
public Func<T, CUIComponent, T> Validate;
|
||||||
|
public bool LayoutProp;
|
||||||
|
public bool DecorProp;
|
||||||
|
public bool AbsoluteProp;
|
||||||
|
public bool ChildProp;
|
||||||
|
public bool ShowInDebug = true;
|
||||||
|
|
||||||
|
public T Value;
|
||||||
|
public void SetValue(T value, [CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (Host == null)
|
||||||
|
{
|
||||||
|
CUI.Log($"{Name} CUIProp doens't have a Host! Type: {typeof(T)} Value: {value} memberName: {memberName}", Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Validate == null)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Value = Validate.Invoke(value, Host);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnSet?.Invoke(value, Host);
|
||||||
|
|
||||||
|
if (ShowInDebug)
|
||||||
|
{
|
||||||
|
CUIDebug.Capture(null, Host, "SetValue", memberName, Name, Value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (LayoutProp) Host.OnPropChanged();
|
||||||
|
if (DecorProp) Host.OnDecorPropChanged();
|
||||||
|
if (AbsoluteProp) Host.OnAbsolutePropChanged();
|
||||||
|
if (ChildProp) Host.OnChildrenPropChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIProp() { }
|
||||||
|
|
||||||
|
public CUIProp(CUIComponent host, string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
174
Quick Interactions/CSharp/Client/CrabUI/Types/CUIRect.cs
Normal file
174
Quick Interactions/CSharp/Client/CrabUI/Types/CUIRect.cs
Normal 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>
|
||||||
|
/// Rectangle with float
|
||||||
|
/// </summary>
|
||||||
|
public struct CUIRect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IDK where to put it
|
||||||
|
/// </summary>
|
||||||
|
public static Rectangle CreateRect(float x, float y, float w, float h)
|
||||||
|
{
|
||||||
|
return new Rectangle((int)Math.Round(x), (int)Math.Round(y), (int)Math.Round(w), (int)Math.Round(h));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public float Left;
|
||||||
|
public float Top;
|
||||||
|
public float Width;
|
||||||
|
public float Height;
|
||||||
|
|
||||||
|
public float Right => Left + Width;
|
||||||
|
public float Bottom => Top + Height;
|
||||||
|
|
||||||
|
public Vector2 Size => new Vector2(Width, Height);
|
||||||
|
public Vector2 Position => new Vector2(Left, Top);
|
||||||
|
public Vector2 Center => new Vector2(Left + Width / 2, Top + Height / 2);
|
||||||
|
public Rectangle Box => new Rectangle((int)Left, (int)Top, (int)Width, (int)Height);
|
||||||
|
|
||||||
|
public Vector2 LeftTop => new Vector2(Left, Top);
|
||||||
|
public Vector2 LeftCenter => new Vector2(Left, Top + Height / 2);
|
||||||
|
public Vector2 LeftBottom => new Vector2(Left, Top + Height);
|
||||||
|
public Vector2 CenterTop => new Vector2(Left + Width / 2, Top);
|
||||||
|
public Vector2 CenterCenter => new Vector2(Left + Width / 2, Top + Height / 2);
|
||||||
|
public Vector2 CenterBottom => new Vector2(Left + Width / 2, Top + Height);
|
||||||
|
public Vector2 RightTop => new Vector2(Left + Width, Top);
|
||||||
|
public Vector2 RightCenter => new Vector2(Left + Width, Top + Height / 2);
|
||||||
|
public Vector2 RightBottom => new Vector2(Left + Width, Top + Height);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public CUIRect Shift(Vector2 shift)
|
||||||
|
{
|
||||||
|
return new CUIRect(Left + shift.X, Top + shift.Y, Width, Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(float x, float y)
|
||||||
|
{
|
||||||
|
return Left < x && x < Right && Top < y && y < Bottom;
|
||||||
|
}
|
||||||
|
public bool Contains(Vector2 pos)
|
||||||
|
{
|
||||||
|
return Left < pos.X && pos.X < Right && Top < pos.Y && pos.Y < Bottom;
|
||||||
|
}
|
||||||
|
public bool Intersect(CUIRect r)
|
||||||
|
{
|
||||||
|
return r.Right >= Left && r.Left <= Right && r.Bottom >= Top && r.Top <= Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle Zoom(float Z)
|
||||||
|
{
|
||||||
|
Vector2 ScreenCenter = CUI.GameScreenSize / 2.0f;
|
||||||
|
Vector2 PosDif = Position - ScreenCenter;
|
||||||
|
Vector2 newPos = PosDif * Z + ScreenCenter;
|
||||||
|
|
||||||
|
return new Rectangle(
|
||||||
|
(int)newPos.X, (int)newPos.Y,
|
||||||
|
(int)(Width / Z), (int)(Height / Z)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public CUIRect(Vector2 size) : this(0, 0, size.X, size.Y) { }
|
||||||
|
public CUIRect(Vector2 position, Vector2 size) : this(position.X, position.Y, size.X, size.Y) { }
|
||||||
|
public CUIRect(float x, float y, float w, float h)
|
||||||
|
{
|
||||||
|
Left = x;
|
||||||
|
Top = y;
|
||||||
|
Width = Math.Max(0f, w);
|
||||||
|
Height = Math.Max(0f, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public override string ToString() => $"[{Left},{Top},{Width},{Height}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rectangle with float?
|
||||||
|
/// </summary>
|
||||||
|
public struct CUINullRect
|
||||||
|
{
|
||||||
|
// Guh...
|
||||||
|
public static CUINullRect Parse(string s)
|
||||||
|
{
|
||||||
|
string content = s.Substring(
|
||||||
|
s.IndexOf('[') + 1,
|
||||||
|
s.IndexOf(']') - s.IndexOf('[') - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
var components = content.Split(',').Select(a => a.Trim());
|
||||||
|
|
||||||
|
string sx = components.ElementAtOrDefault(0);
|
||||||
|
string sy = components.ElementAtOrDefault(1);
|
||||||
|
string sw = components.ElementAtOrDefault(2);
|
||||||
|
string sh = components.ElementAtOrDefault(3);
|
||||||
|
|
||||||
|
float? x = null;
|
||||||
|
float? y = null;
|
||||||
|
float? w = null;
|
||||||
|
float? h = null;
|
||||||
|
|
||||||
|
if (sx == null || sx == "") x = null;
|
||||||
|
else x = float.Parse(sx);
|
||||||
|
|
||||||
|
if (sy == null || sy == "") y = null;
|
||||||
|
else y = float.Parse(sy);
|
||||||
|
|
||||||
|
if (sw == null || sw == "") w = null;
|
||||||
|
else w = float.Parse(sw);
|
||||||
|
|
||||||
|
if (sh == null || sh == "") h = null;
|
||||||
|
else h = float.Parse(sh);
|
||||||
|
|
||||||
|
return new CUINullRect(x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float? Left;
|
||||||
|
public float? Top;
|
||||||
|
public float? Width;
|
||||||
|
public float? Height;
|
||||||
|
|
||||||
|
public Vector2 Size
|
||||||
|
{
|
||||||
|
get => new Vector2(Width ?? 0, Height ?? 0);
|
||||||
|
set { Width = value.X; Height = value.Y; }
|
||||||
|
}
|
||||||
|
public Vector2 Position
|
||||||
|
{
|
||||||
|
get => new Vector2(Left ?? 0, Top ?? 0);
|
||||||
|
set { Left = value.X; Top = value.Y; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 Center => new Vector2(
|
||||||
|
(Left ?? 0) + (Width ?? 0) / 2,
|
||||||
|
(Top ?? 0) + (Height ?? 0) / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
public CUINullRect(Vector2 position, Vector2 size) : this(position.X, position.Y, size.X, size.Y) { }
|
||||||
|
|
||||||
|
public CUINullRect(float? x = null, float? y = null, float? w = null, float? h = null)
|
||||||
|
{
|
||||||
|
Left = x;
|
||||||
|
Top = y;
|
||||||
|
Width = w;
|
||||||
|
Height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"[{Left},{Top},{Width},{Height}]";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
317
Quick Interactions/CSharp/Client/CrabUI/Types/CUISprite.cs
Normal file
317
Quick Interactions/CSharp/Client/CrabUI/Types/CUISprite.cs
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
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.IO;
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
public enum CUISpriteDrawMode
|
||||||
|
{
|
||||||
|
Resize,
|
||||||
|
Wrap,
|
||||||
|
Static,
|
||||||
|
StaticDeep,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper Containing link to texture and drawing settings,
|
||||||
|
/// like SourceRedt, DrawMode, Effects, Rotation...
|
||||||
|
/// Multiple sprites can use the same texture
|
||||||
|
/// </summary>
|
||||||
|
public class CUISprite : ICloneable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 1x1 white texture
|
||||||
|
/// </summary>
|
||||||
|
public static Texture2D BackupTexture => GUI.WhiteTexture;
|
||||||
|
/// <summary>
|
||||||
|
/// new Sprite that uses 1x1 default texture
|
||||||
|
/// </summary>
|
||||||
|
public static CUISprite Default => new CUISprite();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set when you load it from some path
|
||||||
|
/// </summary>
|
||||||
|
public string Path = "";
|
||||||
|
/// <summary>
|
||||||
|
/// None, FlipHorizontally, FlipVertically
|
||||||
|
/// </summary>
|
||||||
|
public SpriteEffects Effects;
|
||||||
|
/// <summary>
|
||||||
|
/// Resize - will resize the sprite to component
|
||||||
|
/// Wrap - will loop the texture
|
||||||
|
/// Static - sprite ignores component position
|
||||||
|
/// </summary>
|
||||||
|
public CUISpriteDrawMode DrawMode;
|
||||||
|
/// <summary>
|
||||||
|
/// Part of the texture that is drawn
|
||||||
|
/// Won't work in Wrap mode becase it will loop the whole texture
|
||||||
|
/// </summary>
|
||||||
|
public Rectangle SourceRect;
|
||||||
|
private Texture2D texture = BackupTexture;
|
||||||
|
/// <summary>
|
||||||
|
/// The link to the texture
|
||||||
|
/// Multiple sprites can use the same texture
|
||||||
|
/// </summary>
|
||||||
|
public Texture2D Texture
|
||||||
|
{
|
||||||
|
get => texture;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
texture = value;
|
||||||
|
SourceRect = new Rectangle(0, 0, texture.Width, texture.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// In radians
|
||||||
|
/// </summary>
|
||||||
|
public float Rotation;
|
||||||
|
/// <summary>
|
||||||
|
/// In degree
|
||||||
|
/// </summary>
|
||||||
|
public float RotationAngle
|
||||||
|
{
|
||||||
|
get => (float)(Rotation * 180 / Math.PI);
|
||||||
|
set => Rotation = (float)(value * Math.PI / 180);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Origin of rotation in pixels
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 Origin;
|
||||||
|
/// <summary>
|
||||||
|
/// Origin of rotation in [0..1] of texture size
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 RelativeOrigin
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Texture == null) return;
|
||||||
|
Origin = new Vector2(value.X * Texture.Width, value.Y * Texture.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Vector2 offset;
|
||||||
|
/// <summary>
|
||||||
|
/// Draw offset from CUIComponent Position
|
||||||
|
/// For your convenience also sets origin
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 Offset
|
||||||
|
{
|
||||||
|
get => offset;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
offset = value;
|
||||||
|
RelativeOrigin = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is not CUISprite b) return false;
|
||||||
|
|
||||||
|
return Texture == b.Texture &&
|
||||||
|
SourceRect == b.SourceRect &&
|
||||||
|
DrawMode == b.DrawMode &&
|
||||||
|
Effects == b.Effects &&
|
||||||
|
Rotation == b.Rotation &&
|
||||||
|
Origin == b.Origin &&
|
||||||
|
Offset == b.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a CUISprite from vanilla Sprite
|
||||||
|
/// </summary>
|
||||||
|
public static CUISprite FromVanilla(Sprite sprite)
|
||||||
|
{
|
||||||
|
if (sprite == null) return Default;
|
||||||
|
|
||||||
|
return new CUISprite(sprite.Texture, sprite.SourceRect)
|
||||||
|
{
|
||||||
|
Path = sprite.FullPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses vanilla GUI sprite from GUIStyle.ComponentStyles with this name
|
||||||
|
/// </summary>
|
||||||
|
public static CUISprite FromName(string name) => FromId(new Identifier(name));
|
||||||
|
public static CUISprite FromId(Identifier id)
|
||||||
|
{
|
||||||
|
GUIComponentStyle? style = GUIStyle.ComponentStyles[id];
|
||||||
|
if (style == null) return Default;
|
||||||
|
|
||||||
|
return FromComponentStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CUISprite FromComponentStyle(GUIComponentStyle style, GUIComponent.ComponentState state = GUIComponent.ComponentState.None)
|
||||||
|
{
|
||||||
|
return FromVanilla(style.Sprites[state].FirstOrDefault()?.Sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO add using construction
|
||||||
|
/// <summary>
|
||||||
|
/// When you load sprite from file, relative paths are considered relative to barotrauma folder
|
||||||
|
/// if BaseFolder != null sprite will check files in BaseFolder first
|
||||||
|
/// Don't forget to set it back to null
|
||||||
|
/// </summary>
|
||||||
|
public static string BaseFolder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default 1x1 white sprite
|
||||||
|
/// </summary>
|
||||||
|
public CUISprite()
|
||||||
|
{
|
||||||
|
Texture = BackupTexture;
|
||||||
|
SourceRect = new Rectangle(0, 0, Texture.Width, Texture.Height);
|
||||||
|
}
|
||||||
|
public CUISprite(string path, Rectangle? sourceRect = null, string baseFolder = null)
|
||||||
|
{
|
||||||
|
baseFolder ??= BaseFolder;
|
||||||
|
string realpath = path;
|
||||||
|
|
||||||
|
if (!System.IO.Path.IsPathRooted(path) && baseFolder != null)
|
||||||
|
{
|
||||||
|
string localPath = System.IO.Path.Combine(baseFolder, path);
|
||||||
|
if (File.Exists(localPath)) realpath = localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path = path;
|
||||||
|
Texture = CUI.TextureManager.GetTexture(realpath);
|
||||||
|
if (sourceRect.HasValue) SourceRect = sourceRect.Value;
|
||||||
|
}
|
||||||
|
public CUISprite(Texture2D texture, Rectangle? sourceRect = null)
|
||||||
|
{
|
||||||
|
Texture = texture ?? BackupTexture;
|
||||||
|
if (sourceRect.HasValue) SourceRect = sourceRect.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Clone()
|
||||||
|
{
|
||||||
|
CUISprite sprite = new CUISprite(Texture, SourceRect)
|
||||||
|
{
|
||||||
|
Path = this.Path,
|
||||||
|
Rotation = this.Rotation,
|
||||||
|
Offset = this.Offset,
|
||||||
|
Origin = this.Origin,
|
||||||
|
Effects = this.Effects,
|
||||||
|
DrawMode = this.DrawMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string mode = DrawMode != CUISpriteDrawMode.Resize ? $", Mode: {DrawMode}" : "";
|
||||||
|
string rect = SourceRect != Texture.Bounds ? $", SourceRect: {CUIExtensions.RectangleToString(SourceRect)}" : "";
|
||||||
|
string effect = Effects != SpriteEffects.None ? $", Effects: {CUIExtensions.SpriteEffectsToString(Effects)}" : "";
|
||||||
|
|
||||||
|
string rotation = Rotation != 0.0f ? $", Rotation: {Rotation}" : "";
|
||||||
|
string offset = Offset != Vector2.Zero ? $", Offset: {CUIExtensions.Vector2ToString(Offset)}" : "";
|
||||||
|
string origin = Origin != Vector2.Zero ? $", Origin: {CUIExtensions.Vector2ToString(Origin)}" : "";
|
||||||
|
|
||||||
|
return $"{{ Path: {Path}{mode}{rect}{effect}{rotation}{offset}{origin} }}";
|
||||||
|
}
|
||||||
|
|
||||||
|
//BUG it can't use absolute paths because of the c://
|
||||||
|
public static CUISprite Parse(string raw)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> props = CUIExtensions.ParseKVPairs(raw);
|
||||||
|
|
||||||
|
if (!props.ContainsKey("path")) return new CUISprite();
|
||||||
|
|
||||||
|
CUISprite sprite = CUI.TextureManager.GetSprite(props["path"]);
|
||||||
|
if (props.ContainsKey("mode"))
|
||||||
|
{
|
||||||
|
sprite.DrawMode = Enum.Parse<CUISpriteDrawMode>(props["mode"]);
|
||||||
|
}
|
||||||
|
if (props.ContainsKey("sourcerect"))
|
||||||
|
{
|
||||||
|
sprite.SourceRect = CUIExtensions.ParseRectangle(props["sourcerect"]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.SourceRect = new Rectangle(0, 0, sprite.Texture.Width, sprite.Texture.Height);
|
||||||
|
}
|
||||||
|
if (props.ContainsKey("effects"))
|
||||||
|
{
|
||||||
|
sprite.Effects = CUIExtensions.ParseSpriteEffects(props["effects"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.ContainsKey("rotation"))
|
||||||
|
{
|
||||||
|
float r;
|
||||||
|
float.TryParse(props["rotation"], out r);
|
||||||
|
sprite.Rotation = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.ContainsKey("offset"))
|
||||||
|
{
|
||||||
|
sprite.Offset = CUIExtensions.ParseVector2(props["offset"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.ContainsKey("origin"))
|
||||||
|
{
|
||||||
|
sprite.Origin = CUIExtensions.ParseVector2(props["origin"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO find less hacky solution
|
||||||
|
public static CUISprite ParseWithContext(string raw, string baseFolder = null)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> props = CUIExtensions.ParseKVPairs(raw);
|
||||||
|
|
||||||
|
if (!props.ContainsKey("path")) return new CUISprite();
|
||||||
|
|
||||||
|
if (!System.IO.Path.IsPathRooted(props["path"]) && baseFolder != null)
|
||||||
|
{
|
||||||
|
string localPath = System.IO.Path.Combine(baseFolder, props["path"]);
|
||||||
|
|
||||||
|
if (File.Exists(localPath)) props["path"] = localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
CUISprite sprite = CUI.TextureManager.GetSprite(props["path"]);
|
||||||
|
if (props.ContainsKey("mode"))
|
||||||
|
{
|
||||||
|
sprite.DrawMode = Enum.Parse<CUISpriteDrawMode>(props["mode"]);
|
||||||
|
}
|
||||||
|
if (props.ContainsKey("sourcerect"))
|
||||||
|
{
|
||||||
|
sprite.SourceRect = CUIExtensions.ParseRectangle(props["sourcerect"]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprite.SourceRect = new Rectangle(0, 0, sprite.Texture.Width, sprite.Texture.Height);
|
||||||
|
}
|
||||||
|
if (props.ContainsKey("effects"))
|
||||||
|
{
|
||||||
|
sprite.Effects = CUIExtensions.ParseSpriteEffects(props["effects"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.ContainsKey("rotation"))
|
||||||
|
{
|
||||||
|
float r;
|
||||||
|
float.TryParse(props["rotation"], out r);
|
||||||
|
sprite.Rotation = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.ContainsKey("offset"))
|
||||||
|
{
|
||||||
|
sprite.Offset = CUIExtensions.ParseVector2(props["offset"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.ContainsKey("origin"))
|
||||||
|
{
|
||||||
|
sprite.Origin = CUIExtensions.ParseVector2(props["origin"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Props implementing this will be bound to their host
|
||||||
|
/// with reflection after creation of the host
|
||||||
|
/// </summary>
|
||||||
|
public interface ICUIVitalizable
|
||||||
|
{
|
||||||
|
public void SetHost(CUIComponent host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ICUIProp : ICUIVitalizable
|
||||||
|
{
|
||||||
|
public void SetName(string name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// For stuff that should be in some way refreshed
|
||||||
|
/// This method appears way too many times
|
||||||
|
/// There are also secret public void CascadeRefresh() method in CUIComponent.Events that refreshes all childs recursivelly
|
||||||
|
/// </summary>
|
||||||
|
public interface IRefreshable
|
||||||
|
{
|
||||||
|
public void Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Quick Interactions/CSharp/Client/CrabUI/Types/Indexer.cs
Normal file
33
Quick Interactions/CSharp/Client/CrabUI/Types/Indexer.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
//xd
|
||||||
|
public class Indexer<TKey, TValue>
|
||||||
|
{
|
||||||
|
public Func<TKey, TValue> Get;
|
||||||
|
public Action<TKey, TValue> Set;
|
||||||
|
|
||||||
|
public TValue this[TKey key]
|
||||||
|
{
|
||||||
|
get => Get(key);
|
||||||
|
set => Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Indexer(Func<TKey, TValue> get, Action<TKey, TValue> set) => (Get, Set) = (get, set);
|
||||||
|
}
|
||||||
|
}
|
||||||
90
Quick Interactions/CSharp/Client/CrabUI/Types/Ranges.cs
Normal file
90
Quick Interactions/CSharp/Client/CrabUI/Types/Ranges.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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>
|
||||||
|
/// Same as Range but with normal ints
|
||||||
|
/// </summary>
|
||||||
|
public struct IntRange
|
||||||
|
{
|
||||||
|
public static IntRange Zero = new IntRange(0, 0);
|
||||||
|
public int Start;
|
||||||
|
public int End;
|
||||||
|
public bool IsZero => Start == 0 && End == 0;
|
||||||
|
public bool IsEmpty => End - Start <= 0;
|
||||||
|
public IntRange(int start, int end)
|
||||||
|
{
|
||||||
|
if (end >= start) (Start, End) = (start, end);
|
||||||
|
else (End, Start) = (start, end);
|
||||||
|
}
|
||||||
|
public static bool operator ==(IntRange a, IntRange b) => a.Start == b.Start && a.End == b.End;
|
||||||
|
public static bool operator !=(IntRange a, IntRange b) => a.Start != b.Start || a.End != b.End;
|
||||||
|
|
||||||
|
public override string ToString() => $"[{Start},{End}]";
|
||||||
|
public static IntRange Parse(string raw)
|
||||||
|
{
|
||||||
|
if (raw == null || raw == "") return new IntRange(0, 0);
|
||||||
|
|
||||||
|
string content = raw.Split('[', ']')[1];
|
||||||
|
|
||||||
|
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
|
||||||
|
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
|
||||||
|
int.TryParse(coords.ElementAtOrDefault(0), out start);
|
||||||
|
int.TryParse(coords.ElementAtOrDefault(1), out end);
|
||||||
|
|
||||||
|
return new IntRange(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Same as Range but with normal floats
|
||||||
|
/// </summary>
|
||||||
|
public struct FloatRange
|
||||||
|
{
|
||||||
|
public static FloatRange Zero = new FloatRange(0, 0);
|
||||||
|
public float Start;
|
||||||
|
public float End;
|
||||||
|
public bool IsZero => Start == 0 && End == 0;
|
||||||
|
public bool IsEmpty => End - Start <= 0;
|
||||||
|
|
||||||
|
public float PosOf(float lambda) => (End - Start) * lambda;
|
||||||
|
public float LambdaOf(float pos) => (pos - Start) / (End - Start);
|
||||||
|
public FloatRange(float start, float end)
|
||||||
|
{
|
||||||
|
if (end >= start) (Start, End) = (start, end);
|
||||||
|
else (End, Start) = (start, end);
|
||||||
|
}
|
||||||
|
public static bool operator ==(FloatRange a, FloatRange b) => a.Start == b.Start && a.End == b.End;
|
||||||
|
public static bool operator !=(FloatRange a, FloatRange b) => a.Start != b.Start || a.End != b.End;
|
||||||
|
|
||||||
|
public override string ToString() => $"[{Start},{End}]";
|
||||||
|
|
||||||
|
public static FloatRange Parse(string raw)
|
||||||
|
{
|
||||||
|
if (raw == null || raw == "") return new FloatRange(0, 0);
|
||||||
|
|
||||||
|
string content = raw.Split('[', ']')[1];
|
||||||
|
|
||||||
|
List<string> coords = content.Split(',').Select(s => s.Trim()).ToList();
|
||||||
|
|
||||||
|
float start;
|
||||||
|
float end;
|
||||||
|
|
||||||
|
float.TryParse(coords.ElementAtOrDefault(0), out start);
|
||||||
|
float.TryParse(coords.ElementAtOrDefault(1), out end);
|
||||||
|
|
||||||
|
return new FloatRange(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Quick Interactions/CSharp/Client/CrabUI/Types/WeakCatalog.cs
Normal file
56
Quick Interactions/CSharp/Client/CrabUI/Types/WeakCatalog.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace QICrabUI
|
||||||
|
{
|
||||||
|
|
||||||
|
public class WeakCatalog<TKey, TValue> where TValue : class
|
||||||
|
{
|
||||||
|
public Dictionary<TKey, List<WeakReference<TValue>>> Pages = new();
|
||||||
|
|
||||||
|
public Dictionary<TKey, List<WeakReference<TValue>>>.KeyCollection Keys => Pages.Keys;
|
||||||
|
|
||||||
|
public void Add(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
if (!Pages.ContainsKey(key)) Pages[key] = new();
|
||||||
|
Pages[key].Add(new WeakReference<TValue>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => Pages.Clear();
|
||||||
|
|
||||||
|
public void RemoveEmptyLinks(TKey key)
|
||||||
|
{
|
||||||
|
if (!Pages.ContainsKey(key))
|
||||||
|
{
|
||||||
|
Pages[key] = new();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pages[key] = Pages[key].Where(wr =>
|
||||||
|
{
|
||||||
|
TValue value = null;
|
||||||
|
wr.TryGetTarget(out value);
|
||||||
|
return value is not null;
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<TValue> GetPage(TKey key)
|
||||||
|
{
|
||||||
|
RemoveEmptyLinks(key);
|
||||||
|
return Pages[key].Select(wr =>
|
||||||
|
{
|
||||||
|
TValue value = null;
|
||||||
|
wr.TryGetTarget(out value);
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Quick Interactions/CSharp/Client/Layers/Logic/Fabricators.cs
Normal file
31
Quick Interactions/CSharp/Client/Layers/Logic/Fabricators.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using QIDependencyInjection;
|
||||||
|
using Barotrauma.Items.Components;
|
||||||
|
|
||||||
|
namespace QuickInteractions
|
||||||
|
{
|
||||||
|
public partial class Fabricators
|
||||||
|
{
|
||||||
|
[Dependency] public FakeInput FakeInput { get; set; }
|
||||||
|
|
||||||
|
public void SelectItem(Item item)
|
||||||
|
{
|
||||||
|
if (Character.Controlled == null) return;
|
||||||
|
|
||||||
|
Character.Controlled.SelectedItem = item;
|
||||||
|
if (GameMain.IsMultiplayer)
|
||||||
|
{
|
||||||
|
FakeInput.SendInteractPackage(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
Quick Interactions/CSharp/Client/Layers/Logic/QuickTalk.cs
Normal file
75
Quick Interactions/CSharp/Client/Layers/Logic/QuickTalk.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using QIDependencyInjection;
|
||||||
|
|
||||||
|
namespace QuickInteractions
|
||||||
|
{
|
||||||
|
[Singleton]
|
||||||
|
public class QuickTalk
|
||||||
|
{
|
||||||
|
[Dependency] public CustomInteractionsTracker CustomInteractionsTracker { get; set; }
|
||||||
|
[Dependency] public FakeInput FakeInput { get; set; }
|
||||||
|
public event Action<Character> CharacterStatusUpdated;
|
||||||
|
|
||||||
|
public IEnumerable<Character> Interactable => Character.CharacterList.Where(
|
||||||
|
character => character.CampaignInteractionType != CampaignMode.InteractionType.None
|
||||||
|
);
|
||||||
|
|
||||||
|
public IEnumerable<Character> WantToTalk => Character.CharacterList.Where(character =>
|
||||||
|
character.CampaignInteractionType == CampaignMode.InteractionType.Talk ||
|
||||||
|
character.CampaignInteractionType == CampaignMode.InteractionType.Examine
|
||||||
|
);
|
||||||
|
|
||||||
|
public IEnumerable<Character> Merchants => Character.CharacterList.Where(character =>
|
||||||
|
character.CampaignInteractionType == CampaignMode.InteractionType.Crew ||
|
||||||
|
character.CampaignInteractionType == CampaignMode.InteractionType.Store ||
|
||||||
|
character.CampaignInteractionType == CampaignMode.InteractionType.Upgrade ||
|
||||||
|
character.CampaignInteractionType == CampaignMode.InteractionType.PurchaseSub ||
|
||||||
|
character.CampaignInteractionType == CampaignMode.InteractionType.MedicalClinic
|
||||||
|
);
|
||||||
|
|
||||||
|
public void InteractWith(Character character)
|
||||||
|
{
|
||||||
|
if (character == null) return;
|
||||||
|
if (Character.Controlled == null) return;
|
||||||
|
if (character.IsDead) return;
|
||||||
|
|
||||||
|
if (character.onCustomInteract != null)
|
||||||
|
{
|
||||||
|
if (GameMain.IsMultiplayer)
|
||||||
|
{
|
||||||
|
FakeInput.SendInteractPackage(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
character.onCustomInteract(character, Character.Controlled);
|
||||||
|
ScheduleCharacterUpdate(character);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CharacterStatusUpdated?.Invoke(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScheduleCharacterUpdate(Character character, int delay = 200)
|
||||||
|
{
|
||||||
|
GameMain.LuaCs.Timer.Wait((object[] args) => CharacterStatusUpdated?.Invoke(character), delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AfterInject()
|
||||||
|
{
|
||||||
|
CustomInteractionsTracker.OnCharacterCreated += (c) => ScheduleCharacterUpdate(c);
|
||||||
|
CustomInteractionsTracker.OnCharacterKilled += (c) => ScheduleCharacterUpdate(c);
|
||||||
|
CustomInteractionsTracker.OnCharacterDespawned += (c) => ScheduleCharacterUpdate(c);
|
||||||
|
CustomInteractionsTracker.OnCustomInteractSet += (c) => ScheduleCharacterUpdate(c);
|
||||||
|
CustomInteractionsTracker.OnConversationEnded += (c) => ScheduleCharacterUpdate(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Quick Interactions/CSharp/Client/Layers/LogicLayer.cs
Normal file
19
Quick Interactions/CSharp/Client/Layers/LogicLayer.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using QIDependencyInjection;
|
||||||
|
|
||||||
|
namespace QuickInteractions
|
||||||
|
{
|
||||||
|
public partial class LogicLayer
|
||||||
|
{
|
||||||
|
[Singleton] public QuickTalk QuickTalk { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using QICrabUI;
|
||||||
|
using QIDependencyInjection;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
|
namespace QuickInteractions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A container for buttons that sync their state
|
||||||
|
/// </summary>
|
||||||
|
public class CUICompositeButton : CUIComponent
|
||||||
|
{
|
||||||
|
[CUISerializable] public Color DisabledColor { get; set; }
|
||||||
|
[CUISerializable] public Color InactiveColor { get; set; }
|
||||||
|
[CUISerializable] public Color MouseOverColor { get; set; }
|
||||||
|
[CUISerializable] public Color MousePressedColor { get; set; }
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CUIButton> Buttons = new();
|
||||||
|
|
||||||
|
public void DetermineColor()
|
||||||
|
{
|
||||||
|
Color cl = Color.Transparent;
|
||||||
|
if (Disabled)
|
||||||
|
{
|
||||||
|
cl = DisabledColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cl = InactiveColor;
|
||||||
|
if (MouseOver) cl = MouseOverColor;
|
||||||
|
if (MousePressed) cl = MousePressedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CUIButton button in Buttons) button.BackgroundColor = cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUICompositeButton() : base()
|
||||||
|
{
|
||||||
|
ConsumeMouseClicks = true;
|
||||||
|
|
||||||
|
OnChildAdded += (child) =>
|
||||||
|
{
|
||||||
|
if (child is CUIButton button)
|
||||||
|
{
|
||||||
|
Buttons.Add(button);
|
||||||
|
button.AutoUpdateColor = false;
|
||||||
|
button.ConsumeMouseClicks = false;
|
||||||
|
OnMouseOff += (e) => DetermineColor();
|
||||||
|
OnMouseOn += (e) => DetermineColor();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DetermineColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Quick Interactions/CSharp/Client/Layers/UI/FabricatorButton.cs
Normal file
100
Quick Interactions/CSharp/Client/Layers/UI/FabricatorButton.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Barotrauma;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using QICrabUI;
|
||||||
|
using QIDependencyInjection;
|
||||||
|
|
||||||
|
namespace QuickInteractions
|
||||||
|
{
|
||||||
|
public class FabricatorButton : CUIHorizontalList
|
||||||
|
{
|
||||||
|
public static Color GetButtonColor(Item item)
|
||||||
|
{
|
||||||
|
return item.Prefab.Identifier.Value switch
|
||||||
|
{
|
||||||
|
"fabricator" => new Color(255, 255, 255),
|
||||||
|
"medicalfabricator" => new Color(255, 130, 130),
|
||||||
|
"deconstructor" => new Color(255, 255, 130),
|
||||||
|
_ => new Color(255, 255, 255),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CUISprite GetIcon(Item item)
|
||||||
|
{
|
||||||
|
return item.Prefab.Identifier.Value switch
|
||||||
|
{
|
||||||
|
"fabricator" => GetIcon(0, 1),
|
||||||
|
"medicalfabricator" => GetIcon(0, 1),
|
||||||
|
"deconstructor" => GetIcon(1, 1),
|
||||||
|
_ => GetIcon(0, 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CUISprite GetIcon(int x, int y) => QuickTalkButton.GetIcon(x, y);
|
||||||
|
|
||||||
|
public static string GetInteractionText(Item item)
|
||||||
|
{
|
||||||
|
return $"{item.Prefab.Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item item { get; set; }
|
||||||
|
|
||||||
|
public bool TextVisible
|
||||||
|
{
|
||||||
|
get => Text.Parent != null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
if (Text.Parent == null) Append(Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Text.Parent != null) RemoveChild(Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CUIButton Icon;
|
||||||
|
public CUITextBlock Text;
|
||||||
|
|
||||||
|
public FabricatorButton(Item item, CUIDirection direction) : base()
|
||||||
|
{
|
||||||
|
FitContent = new CUIBool2(true, true);
|
||||||
|
Direction = direction;
|
||||||
|
|
||||||
|
this["icon"] = Icon = new CUIButton()
|
||||||
|
{
|
||||||
|
Text = "",
|
||||||
|
Border = new CUIBorder(),
|
||||||
|
BackgroundSprite = GetIcon(item),
|
||||||
|
MasterColorOpaque = GetButtonColor(item),
|
||||||
|
Absolute = QuickTalkButton.IconSize,
|
||||||
|
//ResizeToSprite = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.OnMouseDown += (e) =>
|
||||||
|
{
|
||||||
|
DispatchUp(new CUICommand("interact", item));
|
||||||
|
};
|
||||||
|
|
||||||
|
Text = new CUITextBlock("")
|
||||||
|
{
|
||||||
|
TextAlign = CUIAnchor.CenterLeft,
|
||||||
|
Text = GetInteractionText(item),
|
||||||
|
TextScale = QuickTalkButton.TextScale,
|
||||||
|
Ghost = new CUIBool2(false, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user