using Noesis; using Noesis.Interactivity; using System; using UnityEngine; using Color = Noesis.Color; using Grid = Noesis.Grid; using NoesisEventArgs = Noesis.EventArgs; namespace StellarConquest.Presentation.Unity.UI { public enum HideReasonType { None, Programmatic, LightDismiss, } [AssetDependency] public partial class OverlayControl : UserControl, IGameScreenInputProvider { public event EventHandler Shown; public event EventHandler Hidden; public bool IsOverlayVisible { get; private set; } public bool IsLightDismissExteriorEnabled { get; set; } = true; public bool IsLightDismissInteriorEnabled { get; set; } = false; public bool IsBlurEnabled { get; set; } = false; public bool IsBackgroundSourceBlendEnabled { get; set; } = true; public bool IsModal { get; set; } = true; public bool IsFadeEnabled { get; set; } = true; public bool IsUnscaled { get; set; } = false; public bool IsAnimatedShow { get; set; } = false; public bool IsAlternateKeyToCloseEnabled { get; set; } = false; public KeyCode AlternateCloseKey { get; set; } = KeyCode.F; public bool IsDragAllowed { get; set; } = false; public bool IsDragEnabled { get; set; } = false; public bool IsEscapeToCloseEnabled { get; set; } = false; public OverlayLayerType Layer { get; set; } = OverlayLayerType.Primary; public HorizontalAlignment ParentHorizontalAlignment { get; set; } = HorizontalAlignment.Center; public VerticalAlignment ParentVerticalAlignment { get; set; } = VerticalAlignment.Center; public HideReasonType HideReason { get; private set; } = HideReasonType.None; public bool AllowSubsequentPopupDelay { get; protected set; } = true; private Grid _ParentGrid; private OverlayLayer _OverlayLayer; public int OpenFrame { get; private set; } = -1; public int CloseFrame { get; private set; } = -1; public static int CloseFrameGlobal { get; private set; } = -1; public bool UseKeyUpEvent; public OverlayControl() { } protected virtual void InitializeComponent() { if (!Application.isPlaying) return; _OverlayLayer = UIStateMachine.Instance.OverlayLayer; _ParentGrid = new Grid(); _ParentGrid.Name = Name + "_ParentGrid"; _ParentGrid.HorizontalAlignment = HorizontalAlignment.Stretch; _ParentGrid.VerticalAlignment = VerticalAlignment.Stretch; if (IsModal) _ParentGrid.Background = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)); // transparent but interactive else _ParentGrid.Background = null; _ParentGrid.Children.Add(this); } protected virtual void OnInitialized(object sender, NoesisEventArgs args) { if (!Application.isPlaying) return; UpdateLightDismiss(); } public virtual void Show() { _OverlayLayer.Tag = this; PickingManager.Instance.InteractionFired = true; OpenFrame = Time.frameCount; HideReason = HideReasonType.None; if (IsUnscaled) _OverlayLayer.ShowUnscaled(_ParentGrid, Layer, IsBlurEnabled, IsBackgroundSourceBlendEnabled, IsFadeEnabled, IsModal, ParentHorizontalAlignment, ParentVerticalAlignment); else _OverlayLayer.Show(_ParentGrid, Layer, IsBlurEnabled, IsBackgroundSourceBlendEnabled, IsFadeEnabled, IsModal, ParentHorizontalAlignment, ParentVerticalAlignment); if (IsAnimatedShow) this.Grow(_OverlayLayer, TimeSpan.FromMilliseconds(160)); if (Layer == OverlayLayerType.Primary) { IsOverlayVisible = true; UIStateMachine.Instance.AddInputProvider(this); } UpdateWindowDragBehavior(); Shown?.Invoke(this, NoesisEventArgs.Empty); } public virtual void Hide() { _OverlayLayer.Tag = null; PickingManager.Instance.InteractionFired = true; CloseFrame = Time.frameCount; CloseFrameGlobal = CloseFrame; if (HideReason == HideReasonType.None) HideReason = HideReasonType.Programmatic; if (IsUnscaled) _OverlayLayer.HideUnscaled(_ParentGrid, Layer); else _OverlayLayer.Hide(_ParentGrid, Layer, AllowSubsequentPopupDelay); if (Layer == OverlayLayerType.Primary) { IsOverlayVisible = false; UIStateMachine.Instance.RemoveInputProvider(this); } if (UIStateMachine.Instance.IsGameScreenOpen) { if (this == UIStateMachine.Instance.GameScreen.TargetingControl.SelectedTargetMenu) UIStateMachine.Instance.GameScreen.TargetingControl.SelectedTargetMenu = null; } Hidden?.Invoke(this, NoesisEventArgs.Empty); } public bool Toggle() { if (IsOverlayVisible) { Hide(); return false; } else { Show(); return true; } } protected void UpdateWindowDragBehavior() { if (IsDragAllowed && PlayerPreferences.Instance.WindowDragEnabled || IsDragEnabled) { if (Interaction.GetBehaviors(this).Count == 0) { ParentHorizontalAlignment = HorizontalAlignment.Stretch; ParentVerticalAlignment = VerticalAlignment.Stretch; HorizontalAlignment = HorizontalAlignment.Center; VerticalAlignment = VerticalAlignment.Center; MouseDragElementBehavior dragBehavior = new MouseDragElementBehavior(); dragBehavior.ConstrainToParentBounds = true; Interaction.GetBehaviors(this).Add(dragBehavior); } } else { if (Interaction.GetBehaviors(this).Count > 0) Interaction.GetBehaviors(this).Clear(); } } public Vector2 GetViewboxScale() { return _OverlayLayer.GetViewboxScale(); } protected void UpdateLightDismiss() { // Noesis MouseUp event processing runs independent of PickingManager MouseDown processing. The Show() method // is invoked from InteractionEventManager. For this reason, it is possible for PickingManager to request Show() AND // for _ParentGrid.MouseUp to fire immediately thereafter. Variable _InteractionFrame tracks the frame for Show(), and // subsequently ignores mouse events occuring on this same frame. While unfortunate, this is necessary logic to accomodate // parallel threads which have access to the same data structure. The alternative, using Noesis for all UI or Unity for all UI // (ray casting), is not tennable due to the inherent disconnect between 2D (Noesis) and 3D (Picking). _ParentGrid.MouseLeftButtonUp -= LightDismissExteriorMouseUp; if (IsLightDismissExteriorEnabled) _ParentGrid.MouseLeftButtonUp += LightDismissExteriorMouseUp; MouseLeftButtonUp -= LightDismissInteriorMouseUp; if (!IsLightDismissInteriorEnabled) MouseLeftButtonUp += LightDismissInteriorMouseUp; } private void LightDismissExteriorMouseUp(object sender, MouseButtonEventArgs args) { if (OpenFrame != Time.frameCount && !PickingManager.Instance.InteractionFired) { HideReason = HideReasonType.LightDismiss; Hide(); } } private void LightDismissInteriorMouseUp(object sender, MouseButtonEventArgs args) { if (OpenFrame != Time.frameCount && !PickingManager.Instance.InteractionFired) args.Handled = true; } public virtual void UpdateInput() { if (OpenFrame != Time.frameCount) if (!PickingManager.Instance.InteractionFired) if (IsLightDismissExteriorEnabled || IsLightDismissInteriorEnabled || IsEscapeToCloseEnabled) { if (UseKeyUpEvent) { if (Input.GetButtonUp(InputNames.GamePad.B) || Input.GetKeyUp(KeyCode.Escape) || (IsAlternateKeyToCloseEnabled && Input.GetKeyDown(AlternateCloseKey))) Hide(); } else { if (Input.GetButtonDown(InputNames.GamePad.B) || Input.GetKeyDown(KeyCode.Escape) || (IsAlternateKeyToCloseEnabled && Input.GetKeyDown(AlternateCloseKey))) Hide(); } } } } }