diff --git a/CommunityEntity.UI.Slider.cs b/CommunityEntity.UI.Slider.cs new file mode 100644 index 0000000..577529d --- /dev/null +++ b/CommunityEntity.UI.Slider.cs @@ -0,0 +1,70 @@ +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +public partial class CommunityEntity +{ +#if CLIENT + private class SliderEventHandler : MonoBehaviour, IPointerUpHandler, IPointerDownHandler + { + private Slider _slider; + public string onPointerUpCommand; + public string onPointerDownCommand; + private GameObject _toggleGo; + + void Start() + { + _slider = GetComponent(); + if (!_slider) + { + Destroy(this); + } + } + + public void SetToggleGO(GameObject toggleGO) + { + _toggleGo = toggleGO; + if (toggleGO) + { + toggleGO.SetActive(false); + } + } + + public void OnPointerUp(PointerEventData eventData) + { + if (!_slider.interactable) + { + return; + } + + if (_toggleGo) + { + _toggleGo.SetActive(false); + } + + if (!string.IsNullOrEmpty(onPointerUpCommand)) + { + ConsoleNetwork.ClientRunOnServer(onPointerUpCommand + " " + _slider.value.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + } + + public void OnPointerDown(PointerEventData eventData) + { + if (!_slider.interactable) + { + return; + } + + if (_toggleGo) + { + _toggleGo.SetActive(true); + } + + if (!string.IsNullOrEmpty(onPointerDownCommand)) + { + ConsoleNetwork.ClientRunOnServer(onPointerDownCommand + " " + _slider.value.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + } + } +#endif +} \ No newline at end of file diff --git a/CommunityEntity.UI.cs b/CommunityEntity.UI.cs index b49933c..07017a4 100644 --- a/CommunityEntity.UI.cs +++ b/CommunityEntity.UI.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; using UnityEngine.UI; using System; using System.Collections; @@ -813,6 +813,85 @@ T GetOrAddComponent() where T : Component scrollRect.verticalNormalizedPosition = obj.GetFloat("verticalNormalizedPosition", 0f); break; } + + case "UnityEngine.UI.Slider": + { + var slider = GetOrAddComponent(); + HandleEnableState(obj, slider); + + if (ShouldUpdateField("minValue")) + slider.minValue = obj.GetFloat("minValue", 0f); + if (ShouldUpdateField("maxValue")) + slider.maxValue = obj.GetFloat("maxValue", 1f); + if (ShouldUpdateField("wholeNumbers")) + slider.wholeNumbers = obj.GetBoolean("wholeNumbers", false); + if (ShouldUpdateField("interactable")) + slider.interactable = obj.GetBoolean("interactable", true); + if (ShouldUpdateField("direction")) + slider.direction = ParseEnum(obj.GetString("direction"), Slider.Direction.LeftToRight); + + ColorBlock colors = slider.colors; + if (HasField("normalColor")) + colors.normalColor = ColorEx.Parse(obj.GetString("normalColor", "1.0 1.0 1.0 1.0")); + if (HasField("highlightedColor")) + colors.highlightedColor = ColorEx.Parse(obj.GetString("highlightedColor", "1.0 1.0 1.0 1.0")); + if (HasField("pressedColor")) + colors.pressedColor = ColorEx.Parse(obj.GetString("pressedColor", "1.0 1.0 1.0 1.0")); + if (HasField("selectedColor")) + colors.selectedColor = ColorEx.Parse(obj.GetString("selectedColor", "1.0 1.0 1.0 1.0")); + if (HasField("disabledColor")) + colors.disabledColor = ColorEx.Parse(obj.GetString("disabledColor", "0.5 0.5 0.5 0.5")); + if (HasField("colorMultiplier")) + colors.colorMultiplier = obj.GetFloat("colorMultiplier", 1.0f); + if (HasField("fadeDuration")) + colors.fadeDuration = obj.GetFloat("fadeDuration", 0.1f); + + // apply it with fadeDuration of 0 first so the colors instantly apply rather than tweening from img.color + var fadeDur = colors.fadeDuration; + colors.fadeDuration = 0f; + slider.colors = colors; + colors.fadeDuration = fadeDur; + slider.colors = colors; + + if(obj.ContainsKey("handle")) + BuildSliderHandle(slider, obj.GetObject("handle")); + + if (ShouldUpdateField("value")) + { + var v = obj.GetFloat("value", slider.value); + if (allowUpdate && !obj.ContainsKey("setSilently")) + slider.value = v; + else + slider.SetValueWithoutNotify(v); + } + + //Callback when the slider is done being dragged + if ( obj.ContainsKey("onPointerUp") ) + { + SliderEventHandler handler = go.GetComponent() ?? go.AddComponent(); + handler.onPointerUpCommand = obj.GetString("onPointerUp"); + } + + //Callback when the slider starts being dragged + if ( obj.ContainsKey("onPointerDown") ) + { + SliderEventHandler handler = go.GetComponent() ?? go.AddComponent(); + handler.onPointerDownCommand = obj.GetString("onPointerDown"); + } + + // Text to display the value of the slider + if (obj.ContainsKey("text")) + BuildSliderText(slider, obj.GetObject("text")); + + //ID of the UI element that should be toggled on / off when the slider is being dragged + if (obj.ContainsKey("toggleId")) + { + SliderEventHandler handler = go.GetComponent() ?? go.AddComponent(); + handler.SetToggleGO(FindPanel(obj.GetString("toggleId"))); + } + + break; + } } } @@ -897,6 +976,88 @@ private void BuildScrollbar(Scrollbar scrollbar, JSON.Object obj, bool vertical) rt.offsetMax = Vector2.zero; } } + + private void BuildSliderHandle(Slider slider, JSON.Object obj) + { + GameObject handleGO = null; + RectTransform handleRT = null; + if (obj.ContainsKey("handleId")) + { + // Try to find an existing handle for the slider + var existingHandle = FindPanel(obj.GetString("handleId")); + if (existingHandle) + { + handleGO = existingHandle; + handleRT = existingHandle.GetComponent(); + } + } + + // No existing handle was found, create a new one + if (!handleGO) + { + handleGO = new GameObject($"{slider.gameObject.name}___handle", typeof(RectTransform)); + handleRT = handleGO.GetComponent(); + RegisterUi(handleGO); + float size = obj.GetFloat("size", 20f) / 2f; + handleRT.offsetMin = new Vector2(-size, -size); + handleRT.offsetMax = new Vector2(size, size); + } + + handleGO.transform.SetParent(slider.transform, false); + + // Try to get the graphic component, if not found, add one + var graphic = handleGO.GetComponent(); + if (!graphic) + graphic = handleGO.AddComponent(); + + // If the graphic is an image, set any properties we have + if (graphic is Image img) + { + if ( obj.ContainsKey( "sprite" ) ) + img.sprite = FileSystem.Load( obj.GetString( "sprite", "Assets/Content/UI/UI.Background.Tile.psd" ) ); + if ( obj.ContainsKey( "color" ) ) + img.color = ColorEx.Parse( obj.GetString( "color", "1.0 1.0 1.0 1.0" ) ); + if ( obj.ContainsKey( "imagetype" ) ) + img.type = ParseEnum( obj.GetString( "imagetype", "Sliced" ), UnityEngine.UI.Image.Type.Sliced ); + if ( obj.ContainsKey( "material" ) ) + img.material = FileSystem.Load( obj.GetString( "material", "Assets/Icons/IconMaterial.mat" ) ); + if ( obj.ContainsKey( "png" ) && uint.TryParse( obj.GetString( "png" ), out var id ) ) + ApplyTextureToImage( img, id ); + } + + slider.targetGraphic = graphic; + slider.handleRect = handleRT; + } + + private void BuildSliderText(Slider slider, JSON.Object obj) + { + if (!obj.ContainsKey("textId")) + { + return; + } + + var textGO = FindPanel(obj.GetString("textId")); + if (!textGO) + { + return; + } + + Text text = textGO.GetComponent(); + if (!text) + { + return; + } + + string format = obj.GetString("format", "{0:0.00}"); + + text.text = string.Format(format, slider.value); + slider.onValueChanged.RemoveAllListeners(); + slider.onValueChanged.AddListener(value => + { + if(text != null) + text.text = string.Format(format, value); + }); + } // sets the transform to a sensible default private void FitParent(RectTransform transform){ diff --git a/Tests/SliderTest.json b/Tests/SliderTest.json new file mode 100644 index 0000000..f3f7d4d --- /dev/null +++ b/Tests/SliderTest.json @@ -0,0 +1,300 @@ +[ + { + "name": "Slider_Default", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Slider", + "minValue": 0, + "maxValue": 100, + "wholeNumbers": false, + "interactable": true, + "direction": "LeftToRight", + "onPointerUp": "onPointerUp_default", + "onPointerDown": "onPointerDown_default", + "handle": { + "sprite": "Assets/Content/UI/UI.Background.Tile.psd", + "material": "Assets/Icons/IconMaterial.mat", + "color": "0.0 0.0 1.0 1.0", + "size": 20 + } + }, + { + "type": "UnityEngine.UI.Image", + "color": "0 1 0 1" + }, + { + "type": "RectTransform", + "anchormin": "0 1", + "anchormax": "0 1", + "offsetmin": "20 -35", + "offsetmax": "300 -20" + } + ] + }, + + { + "name": "Slider_Label", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Text", + "color": "1 1 1 1", + "fontSize": 16, + "align": "MiddleLeft" + }, + { + "type": "RectTransform", + "anchormin": "0 1", + "anchormax": "0 1", + "offsetmin": "310 -85", + "offsetmax": "500 -60" + } + ] + }, + { + "name": "Slider_With_Label", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Slider", + "minValue": 0, + "maxValue": 100, + "wholeNumbers": false, + "interactable": true, + "direction": "LeftToRight", + "onPointerUp": "onPointerUp_label", + "onPointerDown": "onPointerDown_label", + "handle": { + "sprite": "Assets/Content/UI/UI.Background.Tile.psd", + "material": "Assets/Icons/IconMaterial.mat", + "color": "0.0 0.0 1.0 1.0", + "size": 20 + }, + "text": { + "format": "Value: {0:0.00}", + "textId": "Slider_Label" + } + }, + { + "type": "UnityEngine.UI.Image", + "color": "0 1 0 1" + }, + { + "type": "RectTransform", + "anchormin": "0 1", + "anchormax": "0 1", + "offsetmin": "20 -80", + "offsetmax": "300 -65" + } + ] + }, + + { + "name": "Slider_Custom_Handle", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Image", + "color": "1 0.65 0 1" + }, + { + "type": "RectTransform", + "anchormin": "0.5 0.5", + "anchormax": "0.5 0.5", + "offsetmin": "-10 -10", + "offsetmax": "10 10" + } + ] + }, + { + "name": "Slider_With_Custom Handle", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Slider", + "minValue": 0, + "maxValue": 100, + "wholeNumbers": false, + "interactable": true, + "direction": "LeftToRight", + "onPointerUp": "onPointerUp_handle", + "onPointerDown": "onPointerDown_handle", + "handle": { + "handleId": "Slider_Custom_Handle" + } + }, + { + "type": "UnityEngine.UI.Image", + "color": "0 1 0 1" + }, + { + "type": "RectTransform", + "anchormin": "0 1", + "anchormax": "0 1", + "offsetmin": "20 -125", + "offsetmax": "300 -110" + } + ] + }, + + { + "name": "Slider_Handle_Floating_Label", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Image", + "color": "1 0 0.65 1" + }, + { + "type": "RectTransform", + "anchormin": "0.5 0.5", + "anchormax": "0.5 0.5", + "offsetmin": "-10 -10", + "offsetmax": "10 10" + } + ] + }, + { + "name": "Slider_Floating_Label", + "parent": "Slider_Handle_Floating_Label", + "components": [ + { + "type": "UnityEngine.UI.Text", + "color": "1 1 1 1", + "fontSize": 16, + "align": "MiddleCenter" + }, + { + "type": "RectTransform", + "anchormin": "0.5 0.5", + "anchormax": "0.5 0.5", + "offsetmin": "-75 -40", + "offsetmax": "75 -15" + } + ] + }, + { + "name": "Slider_With_Floating_Label", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Slider", + "minValue": 0, + "maxValue": 100, + "wholeNumbers": false, + "interactable": true, + "direction": "LeftToRight", + "onPointerUp": "onPointerUp_label", + "onPointerDown": "onPointerDown_label", + "handle": { + "handleId": "Slider_Handle_Floating_Label" + }, + "text": { + "format": "{0:0.00}", + "textId": "Slider_Floating_Label" + } + }, + { + "type": "UnityEngine.UI.Image", + "color": "0 1 0 1" + }, + { + "type": "RectTransform", + "anchormin": "0 1", + "anchormax": "0 1", + "offsetmin": "20 -185", + "offsetmax": "300 -170" + } + ] + }, + + { + "name": "Slider_Handle_Toggle", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Image", + "color": "1 0 0.65 1" + }, + { + "type": "RectTransform", + "anchormin": "0.5 0.5", + "anchormax": "0.5 0.5", + "offsetmin": "-10 -10", + "offsetmax": "10 10" + } + ] + }, + { + "name": "Slider_Handle_SubPanel", + "parent": "Slider_Handle_Toggle", + "components": [ + { + "type": "UnityEngine.UI.Image", + "color": "0 0 1 1" + }, + { + "type": "RectTransform", + "anchormin": "0.5 0.5", + "anchormax": "0.5 0.5", + "offsetmin": "-25 -45", + "offsetmax": "25 -20" + } + ] + }, + { + "name": "Slider_Handle_SubPanel_Text", + "parent": "Slider_Handle_SubPanel", + "components": [ + { + "type": "UnityEngine.UI.Text", + "color": "1 1 1 1", + "fontSize": 16, + "align": "MiddleCenter" + }, + { + "type": "RectTransform", + "anchormin": "0 0", + "anchormax": "1 1", + "offsetmin": "0 0", + "offsetmax": "0 0" + } + ] + }, + { + "name": "Slider_With_Toggle_Panel", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Slider", + "minValue": 0, + "maxValue": 100, + "wholeNumbers": false, + "interactable": true, + "direction": "LeftToRight", + "onPointerUp": "onPointerUp_label", + "onPointerDown": "onPointerDown_label", + "toggleId": "Slider_Handle_SubPanel", + "handle": { + "handleId": "Slider_Handle_Toggle" + }, + "text": { + "format": "{0:0.00}", + "textId": "Slider_Handle_SubPanel_Text" + } + }, + { + "type": "UnityEngine.UI.Image", + "color": "0 1 0 1" + }, + { + "type": "RectTransform", + "anchormin": "0 1", + "anchormax": "0 1", + "offsetmin": "20 -255", + "offsetmax": "300 -240" + } + ] + } +] \ No newline at end of file