1
This commit is contained in:
@@ -18,7 +18,9 @@
|
||||
"GUID:517785bb4600a5140b47eac5fa49b8fc",
|
||||
"GUID:838d3286f0973344ab6e99d3951012f7",
|
||||
"GUID:a11ff146d38b27a44af87b4b4d9c4ecb",
|
||||
"GUID:e4d11af1289097a4d9d8987f332a2ae8"
|
||||
"GUID:e4d11af1289097a4d9d8987f332a2ae8",
|
||||
"GUID:3abaaefa7af558d44ba20cea1c43d602",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
8
Src/Unity/Scripts/UX/Cell.meta
Normal file
8
Src/Unity/Scripts/UX/Cell.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea593ccd503f23741b162c0f5a00fb52
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -59,9 +59,10 @@ namespace BITKit.UX
|
||||
GUILayout.BeginVertical();
|
||||
//颜色更改为黑色
|
||||
GUI.color = textColor;
|
||||
GUILayout.Label(_logBuilder.ToString(),style);
|
||||
GUILayout.Label(string.Join("\n",_logBuilder.ToString().Split("\n").Reverse()),style);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -8,7 +8,8 @@
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:d525ad6bd40672747bde77962f1c401e",
|
||||
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
|
||||
"GUID:517785bb4600a5140b47eac5fa49b8fc"
|
||||
"GUID:517785bb4600a5140b47eac5fa49b8fc",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
115
Src/Unity/Scripts/UX/Input/OnScreenButton.cs
Normal file
115
Src/Unity/Scripts/UX/Input/OnScreenButton.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class OnScreenButton : OnScreenControl
|
||||
{
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
private readonly UxmlFloatAttributeDescription m_PressedValueAttribute = new ()
|
||||
{
|
||||
name = "PressedValue",
|
||||
defaultValue = 1f,
|
||||
};
|
||||
|
||||
private readonly UxmlBoolAttributeDescription m_ReleasePressAttribute = new()
|
||||
{
|
||||
name = "ReleasePress"
|
||||
};
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var table = (OnScreenButton)ve;
|
||||
table.PressedValue = m_PressedValueAttribute.GetValueFromBag(bag, cc);
|
||||
table.ReleasePress = m_ReleasePressAttribute.GetValueFromBag(bag, cc);
|
||||
}
|
||||
}
|
||||
public new class UxmlFactory : UxmlFactory<OnScreenButton, UxmlTraits> { }
|
||||
public float PressedValue { get; set; }= 1f;
|
||||
public bool ReleasePress { get; set; }
|
||||
|
||||
private bool _isPressed;
|
||||
private int _pointerId=-1;
|
||||
private Label _label;
|
||||
|
||||
private readonly ValidHandle _isTriggered = new();
|
||||
public OnScreenButton()
|
||||
{
|
||||
RegisterCallback<PointerUpEvent>(OnPointerUp);
|
||||
RegisterCallback<PointerDownEvent>(OnPointerDown);
|
||||
RegisterCallback<PointerMoveEvent>(OnPointerMove);
|
||||
this.AddManipulator(new Clickable(OnClick));
|
||||
_label = this.Create<Label>();
|
||||
|
||||
_isTriggered.AddListener(x =>
|
||||
{
|
||||
if (x)
|
||||
{
|
||||
AddToClassList("selected");
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveFromClassList("selected");
|
||||
}
|
||||
var value = x?PressedValue : 0f;
|
||||
SendValueToControl( value);
|
||||
});
|
||||
}
|
||||
private void OnClick()
|
||||
{
|
||||
|
||||
}
|
||||
private void OnPointerMove(PointerMoveEvent evt)
|
||||
{
|
||||
if(_pointerId!=evt.pointerId)return;
|
||||
|
||||
if (ReleasePress)
|
||||
{
|
||||
_isPressed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isPressed is false)
|
||||
{
|
||||
_isTriggered.AddElement(0);
|
||||
_isPressed = true;
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointerDown(PointerDownEvent evt)
|
||||
{
|
||||
if(_pointerId is not -1)return;
|
||||
|
||||
_pointerId = evt.pointerId;
|
||||
|
||||
_isPressed = true;
|
||||
|
||||
_isTriggered.AddElement(0);
|
||||
}
|
||||
|
||||
private void OnPointerUp(PointerUpEvent evt)
|
||||
{
|
||||
if(_pointerId!=evt.pointerId)return;
|
||||
|
||||
if (ReleasePress && _isPressed)
|
||||
{
|
||||
_isTriggered.AddElement(0);
|
||||
_pointerId = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_pointerId = -1;
|
||||
|
||||
|
||||
_isTriggered.RemoveElement(0);
|
||||
|
||||
}
|
||||
protected override string ControlPathInternal { get; set; }
|
||||
}
|
||||
}
|
11
Src/Unity/Scripts/UX/Input/OnScreenButton.cs.meta
Normal file
11
Src/Unity/Scripts/UX/Input/OnScreenButton.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8afc05714f63ce542a2e2ead78933966
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
330
Src/Unity/Scripts/UX/Input/OnScreenControl.cs
Normal file
330
Src/Unity/Scripts/UX/Input/OnScreenControl.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public abstract class OnScreenControl:VisualElement
|
||||
{
|
||||
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
private readonly UxmlStringAttributeDescription m_ControlPathAttribute = new ()
|
||||
{
|
||||
name = "ControlPath"
|
||||
};
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var table = (OnScreenControl)ve;
|
||||
table.ControlPath = m_ControlPathAttribute.GetValueFromBag(bag, cc);
|
||||
}
|
||||
}
|
||||
|
||||
protected OnScreenControl()
|
||||
{
|
||||
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
|
||||
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
|
||||
IsEnabled.AddDisableElements(this);
|
||||
|
||||
IsEnabled.AddListener(x =>
|
||||
{
|
||||
if (x)
|
||||
OnEnable();
|
||||
else OnDisable();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDetachFromPanel(DetachFromPanelEvent evt)
|
||||
{
|
||||
IsEnabled.AddDisableElements(32);
|
||||
}
|
||||
|
||||
private void OnAttachToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
IsEnabled.RemoveDisableElements(32);
|
||||
}
|
||||
|
||||
private void OnGeometryChanged(GeometryChangedEvent evt)
|
||||
{
|
||||
IsEnabled.SetDisableElements(this, !visible);
|
||||
}
|
||||
|
||||
protected readonly ValidHandle IsEnabled = new();
|
||||
|
||||
/// <summary>
|
||||
/// The control path (see <see cref="InputControlPath"/>) for the control that the on-screen
|
||||
/// control will feed input into.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A device will be created from the device layout referenced by the control path (see
|
||||
/// <see cref="InputControlPath.TryGetDeviceLayout"/>). The path is then used to look up
|
||||
/// <see cref="Control"/> on the device. The resulting control will be fed values from
|
||||
/// the on-screen control.
|
||||
///
|
||||
/// Multiple on-screen controls sharing the same device layout will together create a single
|
||||
/// virtual device. If, for example, one component uses <c>"<Gamepad>/buttonSouth"</c>
|
||||
/// and another uses <c>"<Gamepad>/leftStick"</c> as the control path, a single
|
||||
/// <see cref="Gamepad"/> will be created and the first component will feed data to
|
||||
/// <see cref="Gamepad.buttonSouth"/> and the second component will feed data to
|
||||
/// <see cref="Gamepad.leftStick"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlPath"/>
|
||||
public string ControlPath
|
||||
{
|
||||
get => ControlPathInternal;
|
||||
set
|
||||
{
|
||||
IsEnabled.SetElements(1, string.IsNullOrEmpty(value) is false);
|
||||
ControlPathInternal = value;
|
||||
IsEnabled.AddDisableElements("Force");
|
||||
IsEnabled.RemoveDisableElements("Force");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The actual control that is fed input from the on-screen control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only valid while the on-screen control is enabled. Otherwise, it is <c>null</c>. Also,
|
||||
/// if no <see cref="ControlPath"/> has been set, this will remain <c>null</c> even if the component is enabled.
|
||||
/// </remarks>
|
||||
public InputControl Control => _mControl;
|
||||
|
||||
private InputControl _mControl;
|
||||
private OnScreenControl _mNextControlOnDevice;
|
||||
private InputEventPtr _mInputEventPtr;
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the <see cref="ControlPath"/> of the component. Must be implemented by subclasses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Moving the definition of how the control path is stored into subclasses allows them to
|
||||
/// apply their own <see cref="InputControlAttribute"/> attributes to them and thus set their
|
||||
/// own layout filters.
|
||||
/// </remarks>
|
||||
protected abstract string ControlPathInternal { get; set; }
|
||||
|
||||
private void SetupInputControl()
|
||||
{
|
||||
Debug.Assert(_mControl == null, "InputControl already initialized");
|
||||
Debug.Assert(_mNextControlOnDevice == null, "Previous InputControl has not been properly uninitialized (m_NextControlOnDevice still set)");
|
||||
Debug.Assert(!_mInputEventPtr.valid, "Previous InputControl has not been properly uninitialized (m_InputEventPtr still set)");
|
||||
|
||||
// Nothing to do if we don't have a control path.
|
||||
var path = ControlPathInternal;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return;
|
||||
|
||||
// Determine what type of device to work with.
|
||||
var layoutName = InputControlPath.TryGetDeviceLayout(path);
|
||||
if (layoutName == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Cannot determine device layout to use based on control path '{path}' used in {GetType().Name} component with {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to find existing on-screen device that matches.
|
||||
var internedLayoutName = new InternedString(layoutName);
|
||||
var deviceInfoIndex = -1;
|
||||
for (var i = 0; i < OnScreenDevices.Count; ++i)
|
||||
{
|
||||
////FIXME: this does not take things such as different device usages into account
|
||||
if (OnScreenDevices[i].Device.layout != internedLayoutName) continue;
|
||||
deviceInfoIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we don't have a matching one, create a new one.
|
||||
InputDevice device;
|
||||
if (deviceInfoIndex == -1)
|
||||
{
|
||||
// Try to create device.
|
||||
try
|
||||
{
|
||||
device = InputSystem.AddDevice(layoutName);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Could not create device with layout '{layoutName}' used in '{GetType().Name}' component");
|
||||
Debug.LogException(exception);
|
||||
return;
|
||||
}
|
||||
InputSystem.AddDeviceUsage(device, "OnScreen");
|
||||
|
||||
// Create event buffer.
|
||||
var buffer = StateEvent.From(device, out var eventPtr, Allocator.Persistent);
|
||||
|
||||
// Add to list.
|
||||
deviceInfoIndex = OnScreenDevices.Count;
|
||||
OnScreenDevices.Add(new OnScreenDeviceInfo
|
||||
{
|
||||
EventPtr = eventPtr,
|
||||
Buffer = buffer,
|
||||
Device = device,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
device = OnScreenDevices[deviceInfoIndex].Device;
|
||||
}
|
||||
|
||||
// Try to find control on device.
|
||||
_mControl = InputControlPath.TryFindControl(device, path);
|
||||
if (_mControl == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Cannot find control with path '{path}' on device of type '{layoutName}' referenced by component '{GetType().Name}' with {name}");
|
||||
|
||||
// Remove the device, if we just created one.
|
||||
if (OnScreenDevices[deviceInfoIndex].FirstControl == null)
|
||||
{
|
||||
OnScreenDevices[deviceInfoIndex].Destroy();
|
||||
OnScreenDevices.RemoveAt(deviceInfoIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
_mInputEventPtr = OnScreenDevices[deviceInfoIndex].EventPtr;
|
||||
|
||||
// We have all we need. Permanently add us.
|
||||
OnScreenDevices[deviceInfoIndex] =
|
||||
OnScreenDevices[deviceInfoIndex].AddControl(this);
|
||||
}
|
||||
|
||||
protected void SendValueToControl<TValue>(TValue value)
|
||||
where TValue : struct
|
||||
{
|
||||
if (_mControl == null)
|
||||
return;
|
||||
|
||||
if (!(_mControl is InputControl<TValue> control))
|
||||
throw new ArgumentException(
|
||||
$"The control path {ControlPath} yields a control of type {_mControl.GetType().Name} which is not an InputControl with value type {typeof(TValue).Name}", nameof(value));
|
||||
|
||||
////FIXME: this gives us a one-frame lag (use InputState.Change instead?)
|
||||
_mInputEventPtr.time = InputState.currentTime;
|
||||
control.WriteValueIntoEvent(value, _mInputEventPtr);
|
||||
InputSystem.QueueEvent(_mInputEventPtr);
|
||||
}
|
||||
|
||||
protected void SentDefaultValueToControl()
|
||||
{
|
||||
if (_mControl == null)
|
||||
return;
|
||||
|
||||
////FIXME: this gives us a one-frame lag (use InputState.Change instead?)
|
||||
_mInputEventPtr.time = InputState.currentTime;
|
||||
_mControl.ResetToDefaultStateInEvent(_mInputEventPtr);
|
||||
InputSystem.QueueEvent(_mInputEventPtr);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
SetupInputControl();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_mControl == null)
|
||||
return;
|
||||
|
||||
var device = _mControl.device;
|
||||
for (var i = 0; i < OnScreenDevices.Count; ++i)
|
||||
{
|
||||
if (OnScreenDevices[i].Device != device)
|
||||
continue;
|
||||
|
||||
var deviceInfo = OnScreenDevices[i].RemoveControl(this);
|
||||
if (deviceInfo.FirstControl == null)
|
||||
{
|
||||
// We're the last on-screen control on this device. Remove the device.
|
||||
OnScreenDevices[i].Destroy();
|
||||
OnScreenDevices.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnScreenDevices[i] = deviceInfo;
|
||||
|
||||
// We're keeping the device , but we're disabling the on-screen representation
|
||||
// for one of its controls. If the control isn't in default state, reset it
|
||||
// to that now. This is what ensures that if, for example, OnScreenButton is
|
||||
// disabled after OnPointerDown, we reset its button control to zero even
|
||||
// though we will not see an OnPointerUp.
|
||||
if (!_mControl.CheckStateIsAtDefault())
|
||||
SentDefaultValueToControl();
|
||||
}
|
||||
|
||||
_mControl = null;
|
||||
_mInputEventPtr = new InputEventPtr();
|
||||
Debug.Assert(_mNextControlOnDevice == null);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private struct OnScreenDeviceInfo
|
||||
{
|
||||
public InputEventPtr EventPtr;
|
||||
public NativeArray<byte> Buffer;
|
||||
public InputDevice Device;
|
||||
public OnScreenControl FirstControl;
|
||||
|
||||
public OnScreenDeviceInfo AddControl(OnScreenControl control)
|
||||
{
|
||||
control._mNextControlOnDevice = FirstControl;
|
||||
FirstControl = control;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OnScreenDeviceInfo RemoveControl(OnScreenControl control)
|
||||
{
|
||||
if (FirstControl == control)
|
||||
FirstControl = control._mNextControlOnDevice;
|
||||
else
|
||||
{
|
||||
for (OnScreenControl current = FirstControl._mNextControlOnDevice, previous = FirstControl;
|
||||
current != null; previous = current, current = current._mNextControlOnDevice)
|
||||
{
|
||||
if (current != control)
|
||||
continue;
|
||||
|
||||
previous._mNextControlOnDevice = current._mNextControlOnDevice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
control._mNextControlOnDevice = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (Buffer.IsCreated)
|
||||
Buffer.Dispose();
|
||||
if (Device != null)
|
||||
InputSystem.RemoveDevice(Device);
|
||||
Device = null;
|
||||
Buffer = new NativeArray<byte>();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly List<OnScreenDeviceInfo> OnScreenDevices=new ();
|
||||
|
||||
internal string GetWarningMessage()
|
||||
{
|
||||
return $"{GetType()} needs to be attached as a child to a UI Canvas and have a RectTransform component to function properly.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
Src/Unity/Scripts/UX/Input/OnScreenControl.cs.meta
Normal file
11
Src/Unity/Scripts/UX/Input/OnScreenControl.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d6822f32ddff3b4f9fd962009912a81
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
92
Src/Unity/Scripts/UX/Input/OnScreenGamepad.cs
Normal file
92
Src/Unity/Scripts/UX/Input/OnScreenGamepad.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class OnScreenGamepad : OnScreenControl
|
||||
{
|
||||
public new class UxmlFactory : UxmlFactory<OnScreenGamepad, UxmlTraits> { }
|
||||
protected override string ControlPathInternal { get; set; }
|
||||
private readonly Dictionary<Vector2Int, VisualElement> _buttonMap = new();
|
||||
public OnScreenGamepad()
|
||||
{
|
||||
IsEnabled.AddListener(OnActive);
|
||||
for (var y = 1; y >=-1; y--)
|
||||
{
|
||||
var row = this.Create<VisualElement>();
|
||||
row.style.flexGrow = 1;
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
var dir = (x, y) switch
|
||||
{
|
||||
(-1, 1) => "NW", // 西北
|
||||
(0, 1) => "N", // 北
|
||||
(1, 1) => "NE", // 东北
|
||||
(-1, 0) => "W", // 西
|
||||
(0, 0) => "C", // 中心
|
||||
(1, 0) => "E", // 东
|
||||
(-1, -1) => "SW", // 西南
|
||||
(0, -1) => "S", // 南
|
||||
(1, -1) => "SE", // 东南
|
||||
_ => "NULL" // 其他无效位置
|
||||
};
|
||||
var value = (x, y) switch
|
||||
{
|
||||
(-1, -1) => new float2(-0.707f, -0.707f), // NW 西北
|
||||
(0, -1) =>new float2 (0f, -1f), // N 北
|
||||
(1, -1) =>new float2 (0.707f, -0.707f), // NE 东北
|
||||
(-1, 0) => new float2(-1f, 0f), // W 西
|
||||
(0, 0) =>new float2 (0f, 0f), // C 中心
|
||||
(1, 0) =>new float2 (1f, 0f), // E 东
|
||||
(-1, 1) => new float2(-0.707f, 0.707f), // SW 西南
|
||||
(0, 1) =>new float2 (0f, 1f), // S 南
|
||||
(1, 1) => new float2(0.707f, 0.707f), // SE 东南
|
||||
_ => new float2(0f, 0f) // 默认返回 (0, 0) 无效位置
|
||||
};
|
||||
|
||||
var button = row.Create<VisualElement>();
|
||||
|
||||
|
||||
_buttonMap.TryAdd(new Vector2Int(x, y), button);
|
||||
|
||||
button.style.flexGrow = 1;
|
||||
|
||||
button.AddToClassList("gamepad-button");
|
||||
button.AddToClassList($"gamepad-button--{dir.ToLower()}");
|
||||
|
||||
button.AddManipulator(new Clickable(()=>{}));
|
||||
|
||||
button.RegisterCallback<PointerOverEvent>(_ =>
|
||||
{
|
||||
SendValueToControl((Vector2)value);
|
||||
});
|
||||
button.RegisterCallback<PointerUpEvent>(_ =>
|
||||
{
|
||||
SendValueToControl((Vector2)default);
|
||||
});
|
||||
|
||||
// var label = button.Create<Label>();
|
||||
// label.style.flexGrow = 1;
|
||||
//
|
||||
// label.text = $"{value.x},{value.y}";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActive(bool obj)
|
||||
{
|
||||
if (obj is false)
|
||||
{
|
||||
SendValueToControl((Vector2)default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
11
Src/Unity/Scripts/UX/Input/OnScreenGamepad.cs.meta
Normal file
11
Src/Unity/Scripts/UX/Input/OnScreenGamepad.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bf58213910164e4d8cab38b29830a7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
80
Src/Unity/Scripts/UX/Input/OnScreenStick.cs
Normal file
80
Src/Unity/Scripts/UX/Input/OnScreenStick.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class OnScreenStick:OnScreenControl
|
||||
{
|
||||
public new class UxmlTraits : OnScreenControl.UxmlTraits
|
||||
{
|
||||
private readonly UxmlBoolAttributeDescription m_IsDelteaAttribute = new ()
|
||||
{
|
||||
name = "IsDelta"
|
||||
};
|
||||
|
||||
private readonly UxmlFloatAttributeDescription m_MoveRangeAttribute = new()
|
||||
{
|
||||
name = "MoveRange",defaultValue = 32,
|
||||
};
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var table = (OnScreenStick)ve;
|
||||
table.IsDelta = m_IsDelteaAttribute.GetValueFromBag(bag, cc);
|
||||
table.MoveRange=m_MoveRangeAttribute.GetValueFromBag(bag, cc);
|
||||
}
|
||||
}
|
||||
public new class UxmlFactory : UxmlFactory<OnScreenStick, UxmlTraits> { }
|
||||
public bool IsDelta { get; set; }
|
||||
public float MoveRange { get; set; } = 32;
|
||||
protected override string ControlPathInternal { get; set; }
|
||||
private int _ignoreFrame=1;
|
||||
private Vector2 _startPosition;
|
||||
public OnScreenStick()
|
||||
{
|
||||
RegisterCallback<PointerDownEvent>(OnPointerDown);
|
||||
RegisterCallback<PointerMoveEvent>(OnPointerMove);
|
||||
RegisterCallback<PointerUpEvent>(OnPointerUp);
|
||||
}
|
||||
|
||||
private void OnPointerUp(PointerUpEvent evt)
|
||||
{
|
||||
SendValueToControl(Vector2.zero);
|
||||
_startPosition = evt.position;
|
||||
_ignoreFrame = 1;
|
||||
}
|
||||
|
||||
private void OnPointerDown(PointerDownEvent evt)
|
||||
{
|
||||
_ignoreFrame = 1;
|
||||
_startPosition = evt.position;
|
||||
}
|
||||
|
||||
private void OnPointerMove(PointerMoveEvent evt)
|
||||
{
|
||||
if (_ignoreFrame-- > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Vector2.Distance(evt.deltaPosition, default) > Vector2.Distance(_startPosition, evt.position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = evt.deltaPosition;
|
||||
if (IsDelta)
|
||||
{
|
||||
var newPos = evt.position;
|
||||
pos = new Vector2(newPos.x, newPos.y) - _startPosition;
|
||||
|
||||
pos /= MoveRange;
|
||||
}
|
||||
SendValueToControl(new Vector2(pos.x, -pos.y));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
Src/Unity/Scripts/UX/Input/OnScreenStick.cs.meta
Normal file
11
Src/Unity/Scripts/UX/Input/OnScreenStick.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e068da31183a7244198cbd23b00ebdbf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -11,9 +11,9 @@ namespace BITKit.UX
|
||||
{
|
||||
public UXInputAction(VisualElement visualElement,string controlPathInternal)
|
||||
{
|
||||
this.controlPathInternal = controlPathInternal;
|
||||
this.ControlPathInternal = controlPathInternal;
|
||||
}
|
||||
protected sealed override string controlPathInternal { get; set; }
|
||||
protected sealed override string ControlPathInternal { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
52
Src/Unity/Scripts/UX/Library/CellContainer.cs
Normal file
52
Src/Unity/Scripts/UX/Library/CellContainer.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class CellContainer : VisualElement
|
||||
{
|
||||
public new class UxmlFactory : UxmlFactory<CellContainer, UxmlTraits> { }
|
||||
|
||||
public int CellSize { get; set; } = 64;
|
||||
public CellContainer()
|
||||
{
|
||||
|
||||
RegisterCallback<CustomStyleResolvedEvent>(x =>
|
||||
{
|
||||
MarkDirtyRepaint();
|
||||
});
|
||||
generateVisualContent += GenerateVisualContent;
|
||||
}
|
||||
|
||||
private void GenerateVisualContent(MeshGenerationContext obj)
|
||||
{
|
||||
if(contentRect.height<=0 || contentRect.width<=0)return;
|
||||
|
||||
var painter = obj.painter2D;
|
||||
|
||||
painter.lineWidth = resolvedStyle.borderTopWidth;
|
||||
painter.strokeColor = resolvedStyle.borderTopColor;
|
||||
|
||||
var yCount = contentRect.height / CellSize;
|
||||
var xCount = contentRect.width / CellSize;
|
||||
|
||||
for (var x = 1; x < xCount; x++)
|
||||
{
|
||||
painter.BeginPath();
|
||||
painter.MoveTo(new Vector2(x * CellSize, 0));
|
||||
painter.LineTo(new Vector2(x * CellSize, contentRect.height));
|
||||
painter.Stroke();
|
||||
}
|
||||
for (var y = 1; y < yCount; y++)
|
||||
{
|
||||
painter.BeginPath();
|
||||
painter.MoveTo(new Vector2(0, y * CellSize));
|
||||
painter.LineTo(new Vector2(contentRect.width, y * CellSize));
|
||||
painter.Stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
Src/Unity/Scripts/UX/Library/CellContainer.cs.meta
Normal file
11
Src/Unity/Scripts/UX/Library/CellContainer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b5ba7b7494ea5d42b820dee5a7619c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
#if UNITY_5_3_OR_NEWER && UNITY_WINDOW
|
||||
@@ -11,11 +12,13 @@ using AnotherFileBrowser.Windows;
|
||||
using BITKit.Mod;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UIElements;
|
||||
using Pointer = UnityEngine.InputSystem.Pointer;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class UXModService:UIToolKitPanel,IDisposable
|
||||
public class UXModService:UIToolKitPanel
|
||||
{
|
||||
protected override string DocumentPath => "ux_mod_Service";
|
||||
private const string TemplatePath = "ux_mod_service_template";
|
||||
@@ -37,8 +40,7 @@ namespace BITKit.UX
|
||||
private Label _modDescriptionLabel;
|
||||
[UXBindPath("reload-mod-button",true)]
|
||||
private Button _reloadModButton;
|
||||
[UXBindPath("install-roslyn-fill")]
|
||||
private VisualElement _installRoslynFill;
|
||||
|
||||
|
||||
private readonly ConcurrentDictionary<string,VisualElement> _modContainers=new();
|
||||
public UXModService(IUXService uxService) : base(uxService)
|
||||
@@ -54,8 +56,6 @@ namespace BITKit.UX
|
||||
|
||||
private async UniTask InitializeAsync()
|
||||
{
|
||||
_installRoslynFill.style.width = 0;
|
||||
|
||||
_modTemplate =await ModService.LoadAsset<VisualTreeAsset>(TemplatePath);
|
||||
UXUtils.Inject(this);
|
||||
|
||||
@@ -134,6 +134,7 @@ namespace BITKit.UX
|
||||
private VisualElement Create(IMod mod)
|
||||
{
|
||||
var container =_modsContainer.Create(_modTemplate);
|
||||
container.Get<Toggle>().SetValueWithoutNotify(ModService.Mods.Contains(mod));
|
||||
container.Get<Toggle>().RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (evt.newValue)
|
||||
@@ -145,6 +146,7 @@ namespace BITKit.UX
|
||||
ModService.UnLoad(mod);
|
||||
}
|
||||
});
|
||||
container.Get<Label>().text = mod.Name +"@"+mod.PackageName;
|
||||
container.tooltip = mod.Name+"\n"+mod.Description;
|
||||
|
||||
container.Get<Button>().clicked += () =>
|
||||
@@ -155,8 +157,9 @@ namespace BITKit.UX
|
||||
return container;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
ModService.OnModInstalled-=OnModInstalled;
|
||||
ModService.OnModUnInstalled-=OnModUnInstalled;
|
||||
ModService.OnModLoaded-=OnModLoaded;
|
||||
|
8
Src/Unity/Scripts/UX/RadialMenu.meta
Normal file
8
Src/Unity/Scripts/UX/RadialMenu.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64e4049b9c8014d43bb7431cb56b6a09
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
111
Src/Unity/Scripts/UX/RadialMenu/UXRadialMenu.cs
Normal file
111
Src/Unity/Scripts/UX/RadialMenu/UXRadialMenu.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BITKit.Mod;
|
||||
using BITKit.UX.Hotkey;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class UXRadialMenu : UIToolKitPanel
|
||||
{
|
||||
protected override string DocumentPath => "ui_radial_menu";
|
||||
public override bool CloseWhenClickOutside => true;
|
||||
public override bool AllowCursor => true;
|
||||
private VisualTreeAsset _template;
|
||||
|
||||
[UXBindPath("radialMenu-container")]
|
||||
private VisualElement _container;
|
||||
[UXBindPath("info-label")]
|
||||
private Label _infoLabel;
|
||||
|
||||
public IHotkeyCollection HotkeyCollection { get; set; }
|
||||
|
||||
public UXRadialMenu(IUXService uxService) : base(uxService)
|
||||
{
|
||||
OnInitiatedAsync += InitiatedAsync;
|
||||
}
|
||||
|
||||
private async UniTask InitiatedAsync()
|
||||
{
|
||||
_container.Clear();
|
||||
|
||||
_template =await ModService.LoadAsset<VisualTreeAsset>("ui_radial_menu-template");
|
||||
|
||||
RootVisualElement.RegisterCallback<PointerDownEvent>(x =>
|
||||
{
|
||||
UXService.Return();
|
||||
});
|
||||
}
|
||||
protected override void OnPanelEntry()
|
||||
{
|
||||
base.OnPanelEntry();
|
||||
|
||||
_infoLabel.text = "选择快速动作";
|
||||
|
||||
if (HotkeyCollection is null)
|
||||
{
|
||||
_infoLabel.text = "<color=yellow>没有快速动作</color>";
|
||||
return;
|
||||
}
|
||||
|
||||
var count = HotkeyCollection.Hotkeys.Count();
|
||||
|
||||
if (count is 0)
|
||||
{
|
||||
_infoLabel.text = "<color=yellow>目前没有快速动作</color>";
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var hotkey = HotkeyCollection.Hotkeys.ElementAt(i);
|
||||
|
||||
var angle = 360 / count * i;
|
||||
var pos = Quaternion.Euler(0, 0, angle) * Vector3.up * 384;
|
||||
pos.y *= 0.64f;
|
||||
var container = _container.Create<VisualElement>(_template.CloneTree);
|
||||
|
||||
var button = container.Get<Button>();
|
||||
button.text = hotkey.Name;
|
||||
button.focusable = false;
|
||||
button.clickable = hotkey.HoldDuration is 0 ? new Clickable(OnClick) : null;
|
||||
|
||||
container.style.position = Position.Absolute;
|
||||
container.transform.position = pos;
|
||||
|
||||
container.SetEnabled(hotkey.Enabled);
|
||||
container.RegisterCallback<PointerOverEvent>(OnMouseOver);
|
||||
|
||||
continue;
|
||||
void OnClick()
|
||||
{
|
||||
if (!hotkey.Enabled) return;
|
||||
if (hotkey.OnPerform is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
hotkey.OnPerform();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
UXService.Return();
|
||||
}
|
||||
void OnMouseOver(PointerOverEvent evt)
|
||||
{
|
||||
_infoLabel.text = hotkey.Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
protected override void OnPanelExit()
|
||||
{
|
||||
base.OnPanelExit();
|
||||
|
||||
_container.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
Src/Unity/Scripts/UX/RadialMenu/UXRadialMenu.cs.meta
Normal file
11
Src/Unity/Scripts/UX/RadialMenu/UXRadialMenu.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 961781fb0b2f7004dbb5f7956301c4fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -40,7 +40,6 @@ namespace BITKit.UX
|
||||
uxService.Register(this);
|
||||
InitializeAsync().Forget();
|
||||
}
|
||||
|
||||
private async UniTask InitializeAsync()
|
||||
{
|
||||
await _isBusy;
|
||||
@@ -89,20 +88,20 @@ namespace BITKit.UX
|
||||
|
||||
UXUtils.Inject(this,RootVisualElement);
|
||||
|
||||
OnInitiated?.Invoke();
|
||||
|
||||
RootVisualElement.SetActive(false);
|
||||
|
||||
await OnInitiatedAsync.UniTaskFunc();
|
||||
|
||||
WaitUtilInitialized.TrySetResult();
|
||||
|
||||
RootVisualElement.RegisterCallback<TransitionRunEvent>(OnTransitionRun);
|
||||
RootVisualElement.RegisterCallback<TransitionStartEvent>(OnTransitionStart);
|
||||
RootVisualElement.RegisterCallback<TransitionStartEvent>(OnTransitionStart);
|
||||
RootVisualElement.RegisterCallback<TransitionEndEvent>(OnTransitionEnd);
|
||||
RootVisualElement.RegisterCallback<TransitionCancelEvent>(OnTransitionEnd);
|
||||
|
||||
WaitUtilTransitionCompleted.TrySetResult();
|
||||
|
||||
WaitUtilInitialized.TrySetResult();
|
||||
|
||||
OnInitiated?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +126,7 @@ namespace BITKit.UX
|
||||
public virtual bool AllowReload { get; }
|
||||
public virtual bool AllowCursor { get; }
|
||||
public virtual bool AllowInput { get; }
|
||||
public object Root => RootVisualElement;
|
||||
public virtual string[] InitialUssClasses { get; } = Array.Empty<string>();
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
@@ -152,6 +152,11 @@ namespace BITKit.UX
|
||||
RootVisualElement.SetActive(true);
|
||||
|
||||
//await UniTask.NextFrame();
|
||||
|
||||
if (IsWindow)
|
||||
{
|
||||
RootVisualElement.BringToFront();
|
||||
}
|
||||
|
||||
RootVisualElement.AddToClassList(USSEntry);
|
||||
|
||||
@@ -174,9 +179,19 @@ namespace BITKit.UX
|
||||
{
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
|
||||
await WaitUtilTransitionCompleted.Task;
|
||||
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(1000);
|
||||
await WaitUtilTransitionCompleted.Task.AttachExternalCancellation(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
|
||||
await UniTask.SwitchToMainThread();
|
||||
|
||||
RootVisualElement.AddToClassList(USSEntered);
|
||||
}
|
||||
private void OnTransitionEnd<TChangeEvent>(TChangeEvent evt)
|
||||
@@ -218,8 +233,17 @@ namespace BITKit.UX
|
||||
await UniTask.NextFrame();
|
||||
|
||||
await OnExitAsync.UniTaskFunc();
|
||||
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(1000);
|
||||
await WaitUtilTransitionCompleted.Task.AttachExternalCancellation(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
|
||||
await WaitUtilTransitionCompleted.Task;
|
||||
}
|
||||
void IEntryElement.Exited()
|
||||
{
|
||||
@@ -249,7 +273,7 @@ namespace BITKit.UX
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public virtual void Dispose()
|
||||
{
|
||||
RootVisualElement?.RemoveFromHierarchy();
|
||||
IsDisposed = true;
|
||||
|
141
Src/Unity/Scripts/UX/Service/UI Toolkit/UIToolkitSubPanel.cs
Normal file
141
Src/Unity/Scripts/UX/Service/UI Toolkit/UIToolkitSubPanel.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class UIToolkitSubPanel<T> :IUXPanel where T : IUXPanel
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
protected readonly T Panel;
|
||||
|
||||
protected readonly ValidHandle IsVisible=new();
|
||||
private readonly ValidHandle _busy = new();
|
||||
|
||||
public UIToolkitSubPanel(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
Panel = serviceProvider.GetRequiredService<T>();
|
||||
|
||||
Panel.OnInitiated += OnInitiated;
|
||||
Panel.OnInitiatedAsync += OnInitiatedAsync;
|
||||
|
||||
if (Panel is UIToolKitPanel uiToolKitPanel)
|
||||
{
|
||||
if (uiToolKitPanel.WaitUtilInitialized.Task.Status is UniTaskStatus.Succeeded)
|
||||
{
|
||||
OnInitiatedAsync().Forget();
|
||||
OnInitiated();
|
||||
}
|
||||
}
|
||||
|
||||
Panel.OnEntry +=()=> IsVisible.RemoveDisableElements(this);
|
||||
Panel.OnExit +=()=> IsVisible.AddDisableElements(this);
|
||||
|
||||
IsVisible.AddListener(IsVisibleCallback);
|
||||
}
|
||||
|
||||
private async void IsVisibleCallback(bool x)
|
||||
{
|
||||
await _busy;
|
||||
using var busy = _busy.GetHandle();
|
||||
if (x)
|
||||
{
|
||||
OnEntry?.Invoke();
|
||||
OnEntryAsync?.Invoke();
|
||||
OnEntryCompleted?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnExit?.Invoke();
|
||||
OnExitAsync?.Invoke();
|
||||
OnExitCompleted?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual UniTask OnInitiatedAsync()
|
||||
{
|
||||
UXUtils.Inject(this,Panel.Root as VisualElement);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
protected virtual void OnInitiated()
|
||||
{ UXUtils.Inject(this,Panel.Root as VisualElement);
|
||||
|
||||
}
|
||||
|
||||
bool IEntryElement.IsEntered
|
||||
{
|
||||
get => Panel.IsEntered;
|
||||
set => Panel.IsEntered = value;
|
||||
}
|
||||
|
||||
void IEntryElement.Entry()
|
||||
{
|
||||
Panel.Entry();
|
||||
}
|
||||
|
||||
UniTask IEntryElement.EntryAsync()
|
||||
{
|
||||
return Panel.EntryAsync();
|
||||
}
|
||||
|
||||
void IEntryElement.Entered()
|
||||
{
|
||||
Panel.Entered();
|
||||
}
|
||||
|
||||
void IEntryElement.Exit()
|
||||
{
|
||||
Panel.Exit();
|
||||
}
|
||||
|
||||
UniTask IEntryElement.ExitAsync()
|
||||
{
|
||||
return Panel.ExitAsync();
|
||||
}
|
||||
|
||||
void IEntryElement.Exited()
|
||||
{
|
||||
Panel.Exited();
|
||||
}
|
||||
|
||||
bool IUXPanel.IsWindow => Panel.IsWindow;
|
||||
|
||||
string IUXPanel.Index => Panel.Index;
|
||||
|
||||
bool IUXPanel.AllowCursor => Panel.AllowCursor;
|
||||
|
||||
bool IUXPanel.AllowInput => Panel.AllowInput;
|
||||
|
||||
object IUXPanel.Root => Panel.Root;
|
||||
public event Action OnEntry;
|
||||
public event Func<UniTask> OnEntryAsync;
|
||||
public event Action OnEntryCompleted;
|
||||
public event Action OnExit;
|
||||
public event Func<UniTask> OnExitAsync;
|
||||
public event Action OnExitCompleted;
|
||||
|
||||
event Action IUXPanel.OnInitiated
|
||||
{
|
||||
add => Panel.OnInitiated += value;
|
||||
remove => Panel.OnInitiated -= value;
|
||||
}
|
||||
|
||||
event Func<UniTask> IUXPanel.OnInitiatedAsync
|
||||
{
|
||||
add => Panel.OnInitiatedAsync += value;
|
||||
remove => Panel.OnInitiatedAsync -= value;
|
||||
}
|
||||
|
||||
void IUXPanel.OnTick(float deltaTime)
|
||||
{
|
||||
Panel.OnTick(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b6d7b7e48316f04f9130c5301091ee7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -5,7 +5,10 @@ using System.Threading;
|
||||
using BITKit.Mod;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UIElements;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
@@ -25,9 +28,26 @@ namespace BITKit.UX
|
||||
_serviceProvider = serviceProvider;
|
||||
_cancellationTokenSource = cancellationTokenSource;
|
||||
_entryGroup.OnEntry += OnEntry;
|
||||
|
||||
_windowEntryGroup.OnEntry += OnWindowEntry;
|
||||
_windowEntryGroup.OnExit += OnWindowExit;
|
||||
_ticker.Add(OnTick);
|
||||
}
|
||||
|
||||
|
||||
private void OnWindowExit(IUXPanel obj)
|
||||
{
|
||||
BITAppForUnity.AllowCursor.RemoveElement(_windowEntryGroup);
|
||||
if (obj.AllowInput is false)
|
||||
BITInputSystem.AllowInput.RemoveDisableElements(_windowEntryGroup);
|
||||
}
|
||||
|
||||
private void OnWindowEntry(IUXPanel obj)
|
||||
{
|
||||
BITAppForUnity.AllowCursor.SetElements(_windowEntryGroup, obj.AllowCursor);
|
||||
if (obj.AllowInput is false)
|
||||
BITInputSystem.AllowInput.AddDisableElements(_windowEntryGroup);
|
||||
}
|
||||
|
||||
private readonly EntryGroup<IUXPanel> _entryGroup = new();
|
||||
private readonly EntryGroup<IUXPanel> _windowEntryGroup = new();
|
||||
/// <summary>
|
||||
@@ -73,9 +93,9 @@ namespace BITKit.UX
|
||||
|
||||
public string SettingsPath { get; set; } = "ux_panel_settings";
|
||||
public object Root { get; private set; }
|
||||
public static VisualElement RootVisualElement { get; private set; }
|
||||
public async UniTask InitializeAsync()
|
||||
{
|
||||
|
||||
var gameObject = new GameObject("UXService");
|
||||
Object.DontDestroyOnLoad(gameObject);
|
||||
|
||||
@@ -87,6 +107,11 @@ Object.Destroy(gameObject);
|
||||
var document = gameObject.AddComponent<UIDocument>();
|
||||
try
|
||||
{
|
||||
if (Touchscreen.current is not null && SettingsPath == "ux_panel_settings")
|
||||
{
|
||||
SettingsPath = "ux_panel_settings_mobile";
|
||||
}
|
||||
|
||||
var panelSettings =await ModService.LoadAsset<PanelSettings>(SettingsPath);
|
||||
document.panelSettings = panelSettings;
|
||||
}
|
||||
@@ -96,8 +121,12 @@ Object.Destroy(gameObject);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
Root = document.rootVisualElement;
|
||||
Root = RootVisualElement= document.rootVisualElement;
|
||||
|
||||
if (Touchscreen.current is not null)
|
||||
{
|
||||
RootVisualElement.AddToClassList("mobile");
|
||||
}
|
||||
}
|
||||
|
||||
public void Register(IUXPanel panel) => _registryQueue.Enqueue(panel);
|
||||
@@ -115,6 +144,22 @@ Object.Destroy(gameObject);
|
||||
public void Entry(string panelName) => _entryQueueByName.TryAdd(panelName);
|
||||
public IUXPanel CurrentPanel => _currentPanel;
|
||||
public event Action<IUXPanel, IUXPanel> OnPanelChanged;
|
||||
public bool TryPick(float2 position, out object obj)
|
||||
{
|
||||
obj = null;
|
||||
if (!EventSystem.current.IsPointerOverGameObject())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
position.y = Screen.height - position.y;
|
||||
|
||||
var ve = RootVisualElement.panel.Pick(RuntimePanelUtils.ScreenToPanel(RootVisualElement.panel, position));
|
||||
|
||||
obj = ve;
|
||||
|
||||
return obj is not null;
|
||||
}
|
||||
|
||||
public void Return()
|
||||
{
|
||||
@@ -143,6 +188,7 @@ Object.Destroy(gameObject);
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
while (_registryQueue.TryDequeue(out var result))
|
||||
{
|
||||
if (result is null) continue;
|
||||
@@ -201,6 +247,13 @@ Object.Destroy(gameObject);
|
||||
|
||||
public async void Dispose()
|
||||
{
|
||||
foreach (var panelsValue in _panels.Values)
|
||||
{
|
||||
if (panelsValue is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
_ticker.Remove(OnTick);
|
||||
await UniTask.SwitchToMainThread();
|
||||
if (_currentPanel is not null)
|
||||
|
@@ -3,6 +3,8 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
#if UNITY_EDITOR
|
||||
@@ -10,103 +12,106 @@ using Types = FullscreenEditor.Types;
|
||||
#endif
|
||||
namespace BITKit.UX.Settings
|
||||
{
|
||||
public class UXSettings : MonoBehaviour
|
||||
public interface IUXSettings:IUXPanel{}
|
||||
public class UXSettings<T,TValue> : UIToolkitSubPanel<T>,IUXSettings where T: IUXPanel
|
||||
{
|
||||
|
||||
[SerializeField] private UIDocument document;
|
||||
|
||||
[UXBindPath("resolution-dropdown")]
|
||||
private DropdownField _resolutionDropdown;
|
||||
[UXBindPath("sensitivity-slider")]
|
||||
private Slider _sensitivitySlider;
|
||||
[UXBindPath("fullscreen-toggle")]
|
||||
private Toggle _fullscreenToggle;
|
||||
|
||||
private void Start()
|
||||
private readonly TValue _value;
|
||||
private readonly IWrapper<TValue> _valueWrapper;
|
||||
public UXSettings(IServiceProvider serviceProvider, IWrapper<TValue> valueWrapper) : base(serviceProvider)
|
||||
{
|
||||
UXUtils.Inject(this);
|
||||
|
||||
|
||||
|
||||
_resolutionDropdown.choices = Screen.resolutions.Select(x => x.ToString()).ToList();
|
||||
_resolutionDropdown.SetValueWithoutNotify(Screen.currentResolution.ToString());
|
||||
_resolutionDropdown.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
Data.Set(Constant.Environment.mat_setvideomode, x.newValue);
|
||||
});
|
||||
|
||||
_fullscreenToggle.SetValueWithoutNotify(Screen.fullScreen);
|
||||
_fullscreenToggle.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
Data.Set(Constant.Environment.fullscreen, x.newValue);
|
||||
});
|
||||
|
||||
//_sensitivitySlider.SetValueWithoutNotify(PlayerConfig.Singleton.Sensitivity);
|
||||
_sensitivitySlider.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
Data.Set(Constant.Environment.sensitivity, x.newValue);
|
||||
});
|
||||
|
||||
Data.AddListener<string>(Constant.Environment.mat_setvideomode, OnResolutionChanged);
|
||||
Data.AddListener<bool>(Constant.Environment.fullscreen,OnFullScreenChanged);
|
||||
Data.AddListener<float>(Constant.Environment.sensitivity,OnSensitivityChanged);
|
||||
|
||||
destroyCancellationToken.Register(() =>
|
||||
{
|
||||
Data.RemoveListender<string>(Constant.Environment.mat_setvideomode, OnResolutionChanged);
|
||||
Data.RemoveListender<bool>(Constant.Environment.fullscreen,OnFullScreenChanged);
|
||||
Data.RemoveListender<float>(Constant.Environment.sensitivity,OnSensitivityChanged);
|
||||
|
||||
PlayerPrefs.Save();
|
||||
});
|
||||
|
||||
if(PlayerPrefs.HasKey(Constant.Environment.fullscreen))
|
||||
Data.Set(Constant.Environment.fullscreen, PlayerPrefs.GetInt(Constant.Environment.fullscreen) == 1);
|
||||
if(PlayerPrefs.HasKey(Constant.Environment.sensitivity))
|
||||
Data.Set(Constant.Environment.sensitivity, PlayerPrefs.GetFloat(Constant.Environment.sensitivity));
|
||||
_valueWrapper = valueWrapper;
|
||||
_value = _valueWrapper.Value;
|
||||
}
|
||||
|
||||
private async void OnSensitivityChanged(float obj)
|
||||
{
|
||||
await UniTask.SwitchToMainThread(destroyCancellationToken);
|
||||
|
||||
_sensitivitySlider.SetValueWithoutNotify(obj);
|
||||
|
||||
PlayerPrefs.SetFloat(Constant.Environment.sensitivity,obj);
|
||||
}
|
||||
|
||||
private async void OnFullScreenChanged(bool obj)
|
||||
{
|
||||
await UniTask.SwitchToMainThread(destroyCancellationToken);
|
||||
_fullscreenToggle.SetValueWithoutNotify(obj);
|
||||
|
||||
[UXBindPath("settings-container")]
|
||||
private VisualElement _settingsContainer;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (obj)
|
||||
private void Save()
|
||||
{
|
||||
_valueWrapper.Value = _value;
|
||||
}
|
||||
protected override void OnInitiated()
|
||||
{
|
||||
base.OnInitiated();
|
||||
_settingsContainer.Clear();
|
||||
foreach (var propertyInfo in typeof(TValue).GetProperties())
|
||||
{
|
||||
FullscreenEditor.Fullscreen.MakeFullscreen(Types.GameView);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var x in FullscreenEditor.Fullscreen.GetAllFullscreen())
|
||||
var name = propertyInfo.GetDisplayName();
|
||||
var value = propertyInfo.GetValue(_valueWrapper.Value);
|
||||
switch (propertyInfo.PropertyType)
|
||||
{
|
||||
x.Close();
|
||||
case var type when type == typeof(bool):
|
||||
{
|
||||
var checkBox = _settingsContainer.Create<Toggle>();
|
||||
checkBox.label = name;
|
||||
checkBox.SetValueWithoutNotify(value.As<bool>());
|
||||
checkBox.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
propertyInfo.SetValue(_value,x.newValue);
|
||||
Save();
|
||||
});
|
||||
}
|
||||
break;
|
||||
case var type when type == typeof(float):
|
||||
{
|
||||
var slider = _settingsContainer.Create<Slider>();
|
||||
slider.label = name;
|
||||
slider.showInputField = true;
|
||||
slider.showMixedValue = true;
|
||||
slider.lowValue = 1;
|
||||
slider.highValue = 100;
|
||||
slider.SetValueWithoutNotify(value.As<float>());
|
||||
slider.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
propertyInfo.SetValue(_value,x.newValue);
|
||||
Save();
|
||||
});
|
||||
}
|
||||
break;
|
||||
case var type when type == typeof(int):
|
||||
{
|
||||
var slider = _settingsContainer.Create<SliderInt>();
|
||||
slider.label = name;
|
||||
slider.showInputField = true;
|
||||
slider.showMixedValue = true;
|
||||
slider.lowValue = 1;
|
||||
slider.highValue = 100;
|
||||
slider.SetValueWithoutNotify(value.As<int>());
|
||||
slider.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
propertyInfo.SetValue(_value,x.newValue);
|
||||
Save();
|
||||
});
|
||||
}
|
||||
break;
|
||||
case var type when type == typeof(string):
|
||||
{
|
||||
var textField = _settingsContainer.Create<TextField>();
|
||||
textField.label = name;
|
||||
textField.SetValueWithoutNotify(value.As<string>());
|
||||
textField.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
propertyInfo.SetValue(_value,x.newValue);
|
||||
Save();
|
||||
});
|
||||
}
|
||||
break;
|
||||
case var type when type.IsEnum:
|
||||
{
|
||||
var enumField = _settingsContainer.Create<EnumField>();
|
||||
enumField.label = name;
|
||||
|
||||
enumField.Init(value as Enum);
|
||||
enumField.SetValueWithoutNotify(value as Enum);
|
||||
enumField.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
propertyInfo.SetValue(_value,x.newValue);
|
||||
Save();
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
Screen.fullScreen = obj;
|
||||
#endif
|
||||
PlayerPrefs.SetInt(Constant.Environment.fullscreen, obj ? 1 : 0);
|
||||
}
|
||||
private async void OnResolutionChanged(string newResolution)
|
||||
{
|
||||
await UniTask.SwitchToMainThread(destroyCancellationToken);
|
||||
|
||||
_resolutionDropdown.SetValueWithoutNotify(newResolution);
|
||||
var resolution = Screen.resolutions.Single(x => x.ToString() == newResolution);
|
||||
|
||||
Screen.SetResolution(resolution.width,resolution.height,Data.Get<bool>(Constant.Environment.fullscreen));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -84,7 +84,18 @@ namespace BITKit.UX
|
||||
return "";
|
||||
}
|
||||
|
||||
var screenPosition = Mouse.current.position.ReadValue();
|
||||
var screenPosition = Vector2.zero;
|
||||
|
||||
if (Mouse.current is not null)
|
||||
{
|
||||
screenPosition= Mouse.current.position.ReadValue();
|
||||
}
|
||||
|
||||
if (Touchscreen.current is not null)
|
||||
{
|
||||
screenPosition = Touchscreen.current.position.ReadValue();
|
||||
}
|
||||
|
||||
screenPosition.y = Screen.height - screenPosition.y;
|
||||
|
||||
VisualElement ve = panel.Pick(RuntimePanelUtils.ScreenToPanel(panel, screenPosition));
|
||||
|
Reference in New Issue
Block a user