Add quick interactions locally
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user