Untitled

 avatar
unknown
plain_text
10 months ago
20 kB
11
Indexable
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Visualization.Tools.UIAssets.TextureData;
using Visualization.Tools.UIAssets.Panels;

namespace Visualization.Tools.UIAssets.Viewport
{
    public class ViewportManager : MonoBehaviour
    {
        private enum EditMode { HORIZONTAL, VERTICAL, CORNER, MAXIMIZED, NONE };

        [SerializeField] private Camera _mainCamera;
        [SerializeField] private ViewportLayout _currentLayout;
        public ViewportLayout CurrentLayout
        {
            set { _currentLayout = value; PopulateViewports(_currentLayout); }
        }

        private EditMode _editMode = EditMode.NONE;
        [SerializeField] private bool _canEditBorders = true;

        [SerializeField] private float _screenBorderThreshold = 10f;
        private Vector2 _viewportBorderThreshold;

        [SerializeField] private List<Viewport> _viewports = new List<Viewport>();
        public List<Viewport> Viewports => _viewports;

        private const int VIEWPORT_SHOW_DISPLAY = 0;
        private const int VIEWPORT_HIDE_DISPLAY = 7;
        private const int SHOW_DISPLAY = 0;

        [SerializeField] private CursorTextureData _sideBorderCursorTextureData;
        [SerializeField] private CursorTextureData _topBottomBorderCursorTextureData;
        [SerializeField] private CursorTextureData _cornerCursorTextureData;
        [SerializeField] private TextureData.TextureData _clockWiseTextureData;
        [SerializeField] private TextureData.TextureData _counterClockwiseTextureData;
        [SerializeField] private TextureData.TextureData _maximizeWindowTextureData;
        [SerializeField] private TextureData.TextureData _minimizeWindowTextureData;

        private Vector3 _currentMousePosition;
        private Vector3 _lastMousePosition;
        private Vector3 _mousePositionDelta;
        private Vector2 _viewportPosition;

        private Viewport _activeViewport;
        private Viewport _lastFocused;

        private bool _isMaximized;
        private Rect? _preMaximizedRect;
        private float? _preCameraPriority;

        private bool _nearTopBorder, _nearBottomBorder, _nearRightBorder, _nearLeftBorder = false;

        private bool _shiftPressed = false;
        private bool _spacePressed = false;
        private bool _leftControlPressed = false;
        private bool _tabPressed = false;

        private readonly Dictionary<Camera, float> _defaultDepths = new();

        private void Setup()
        {
            _sideBorderCursorTextureData       ??= Resources.Load<CursorTextureData>("TextureData/LeftRightBorderCursor");
            _topBottomBorderCursorTextureData  ??= Resources.Load<CursorTextureData>("TextureData/TopBottomBorderCursor");
            _cornerCursorTextureData           ??= Resources.Load<CursorTextureData>("TextureData/CornerCursor");
            _clockWiseTextureData              ??= Resources.Load<TextureData.TextureData>("TextureData/ClockwiseScreen");
            _counterClockwiseTextureData       ??= Resources.Load<TextureData.TextureData>("TextureData/CounterClockwiseScreen");
            _maximizeWindowTextureData         ??= Resources.Load<TextureData.TextureData>("TextureData/MaximizeWindowTextureData");
            _minimizeWindowTextureData         ??= Resources.Load<TextureData.TextureData>("TextureData/MinimizeWindowTextureData");
        }

        private void Reset()  { Setup(); }
        private void Awake()  { Setup(); }

        private void Start()
        {
            var sceneViewportIDs = FindObjectsOfType<ViewportID>();
            foreach (var sceneViewportID in sceneViewportIDs) HideViewport(sceneViewportID);

            PopulateViewports();

            foreach (var v in _viewports)
            {
                var cam = v.ViewportID?.ViewportCamera;
                if (!cam) continue;
                if (!_defaultDepths.ContainsKey(cam)) _defaultDepths[cam] = cam.depth;
                if (!cam.TryGetComponent(out PhysicsRaycaster _))
                    cam.gameObject.AddComponent<PhysicsRaycaster>();
            }
        }

        private void Update()
        {
            _currentMousePosition = Input.mousePosition;
            _mousePositionDelta = _currentMousePosition - _lastMousePosition;

            if (!_isMaximized)
            {
                var top = GetTopmostViewportUnderPointer();
                if (top != _lastFocused && top != null)
                {
                    EnableOnly(top);
                    _lastFocused = top;
                }
            }

            PerformEditModeAction();
            _lastMousePosition = _currentMousePosition;
        }

        public void Subscribe(Viewport viewport)   { _viewports.Add(viewport); }
        public void Unsubscribe(Viewport viewport) { _viewports.Remove(viewport); }

        private void PerformEditModeAction()
        {
            switch (_editMode)
            {
                case EditMode.HORIZONTAL:
                    if (_currentMousePosition.x < 0 || _currentMousePosition.x > Screen.width) break;
                    if (_nearLeftBorder)  ShiftViewportBorderByPixel(_activeViewport, (_mousePositionDelta.x, 0, 0, 0));
                    else if (_nearRightBorder) ShiftViewportBorderByPixel(_activeViewport, (0, _mousePositionDelta.x, 0, 0));
                    break;

                case EditMode.VERTICAL:
                    if (_currentMousePosition.y < 0 || _currentMousePosition.y > Screen.height) break;
                    if (_nearTopBorder)    ShiftViewportBorderByPixel(_activeViewport, (0, 0, _mousePositionDelta.y, 0));
                    if (_nearBottomBorder) ShiftViewportBorderByPixel(_activeViewport, (0, 0, 0, _mousePositionDelta.y));
                    break;

                case EditMode.MAXIMIZED:
                    CheckKeyEvents();
                    break;

                case EditMode.NONE:
                    if (HasMouseMoved() && !_isMaximized) UpdateManagerDataOnViewport();
                    CheckKeyEvents();
                    CaptureEditModeChange();
                    break;
            }
            if (Input.GetKeyUp(KeyCode.Mouse0)) _editMode = EditMode.NONE;
        }

        private void UpdateManagerDataOnViewport()
        {
            _nearLeftBorder = _nearRightBorder = _nearBottomBorder = _nearTopBorder = false;

            foreach (Viewport viewport in _viewports)
            {
                if (!IsPointerInViewport(_currentMousePosition, viewport)) continue;

                _activeViewport = viewport;
                _viewportPosition = viewport.ScreenToCustomViewport(_currentMousePosition);

                _viewportBorderThreshold = new Vector2(
                    _screenBorderThreshold / viewport.PixelWidth,
                    _screenBorderThreshold / viewport.PixelHeight);

                _nearLeftBorder   = _viewportPosition.x < _viewportBorderThreshold.x;
                _nearRightBorder  = _viewportPosition.x > 1 - _viewportBorderThreshold.x;
                _nearBottomBorder = _viewportPosition.y < _viewportBorderThreshold.y;
                _nearTopBorder    = _viewportPosition.y > 1 - _viewportBorderThreshold.y;

                break;
            }
        }

        private void CaptureEditModeChange()
        {
            if (!_canEditBorders) return;

            if (IsNearCorner(_viewportPosition))
            {
                Cursor.SetCursor(_cornerCursorTextureData.Texture, _cornerCursorTextureData.Hotspot, CursorMode.Auto);
                if (Input.GetKeyDown(KeyCode.Mouse0)) _editMode = EditMode.CORNER;
            }
            else if (_nearRightBorder || _nearLeftBorder)
            {
                Cursor.SetCursor(_sideBorderCursorTextureData.Texture, _sideBorderCursorTextureData.Hotspot, CursorMode.Auto);
                if (Input.GetKeyDown(KeyCode.Mouse0)) _editMode = EditMode.HORIZONTAL;
            }
            else if (_nearTopBorder || _nearBottomBorder)
            {
                Cursor.SetCursor(_topBottomBorderCursorTextureData.Texture, _topBottomBorderCursorTextureData.Hotspot, CursorMode.Auto);
                if (Input.GetKeyDown(KeyCode.Mouse0)) _editMode = EditMode.VERTICAL;
            }
            else
            {
                Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
            }

            if (Input.GetKeyUp(KeyCode.Mouse0)) _editMode = EditMode.NONE;
        }

        private void CheckKeyEvents()
        {
            if (Input.GetKeyDown(KeyCode.LeftShift))    _shiftPressed = true;
            if (Input.GetKeyUp(KeyCode.LeftShift))      _shiftPressed = false;

            if (Input.GetKeyDown(KeyCode.Space))        _spacePressed = true;
            if (Input.GetKeyUp(KeyCode.Space))          _spacePressed = false;

            if (Input.GetKeyDown(KeyCode.LeftControl))  _leftControlPressed = true;
            if (Input.GetKeyUp(KeyCode.LeftControl))    _leftControlPressed = false;

            if (Input.GetKeyDown(KeyCode.Tab))          _tabPressed = true;
            if (Input.GetKeyUp(KeyCode.Tab))            _tabPressed = false;

            if (_tabPressed)
            {
                if (!_isMaximized)
                {
                    if (_shiftPressed) ShiftViewportsBackward();
                    else               ShiftViewportsForward();
                    _tabPressed = false;
                }
                return;
            }

            if (_spacePressed && _leftControlPressed)
            {
                ToggleMaximizedView();
                _spacePressed = false;
                return;
            }
        }

        private void ShiftViewports(bool direction)
        {
            var tempList = _viewports.Where(v => v.ViewportID.ViewportType.Type != "UI").ToList();
            tempList = direction ? tempList.AsEnumerable().Reverse().ToList() : tempList;

            var tempID = tempList[0].ViewportID;
            for (int i = 0; i < tempList.Count - 1; i++)
                tempList[i].ViewportID = tempList[i + 1].ViewportID;

            tempList[^1].ViewportID = tempID;
        }

        public void ShiftViewportsForward()
        {
            var imagePopup = FindObjectOfType<ImagePopup>();
            if (imagePopup != null) imagePopup.ShowImage(_clockWiseTextureData.Texture, 0.5f, 0.5f);
            ShiftViewports(true);
        }
        public void ShiftViewportsBackward()
        {
            var imagePopup = FindObjectOfType<ImagePopup>();
            if (imagePopup != null) imagePopup.ShowImage(_counterClockwiseTextureData.Texture, 0.5f, 0.5f);
            ShiftViewports(false);
        }

        public void ToggleMaximizedView()
        {
            _isMaximized = !_isMaximized;
            SetMaximizedView(_isMaximized);
        }

        private void SetMaximizedView(bool isMaximized)
        {
            if (isMaximized)
            {
                if (_activeViewport == null)
                {
                    _activeViewport = GetTopmostViewportUnderPointer() ?? _viewports.FirstOrDefault();
                    if (_activeViewport == null) return;
                }

                _preMaximizedRect = new Rect(_activeViewport.ViewportRect);
                _preCameraPriority = _activeViewport.ViewportCamera.depth;

                _activeViewport.ViewportRect = new Rect(0, 0, 1, 1);
                _activeViewport.ViewportCamera.depth = 999;

                EnableOnly(_activeViewport);

                var imagePopup = FindObjectOfType<ImagePopup>();
                if (imagePopup != null) imagePopup.ShowImage(_maximizeWindowTextureData.Texture, 0.5f, 0.5f);

                _editMode = EditMode.MAXIMIZED;
                _isMaximized = true;
            }
            else
            {
                if (_preMaximizedRect == null || _preCameraPriority == null) return;

                _activeViewport.ViewportRect = (Rect)_preMaximizedRect;
                _activeViewport.ViewportCamera.depth = (float)_preCameraPriority;
                _preMaximizedRect = null;
                _preCameraPriority = null;

                RestoreAll();

                var img = FindObjectOfType<ImagePopup>();
                if (img != null) img.ShowImage(_minimizeWindowTextureData.Texture, 0.5f, 0.5f);

                _editMode = EditMode.NONE;
                _isMaximized = false;
                _lastFocused = null;
            }
        }

        private void ShiftViewportBorderByPixel(
            Viewport viewport,
            (float leftPixelDelta, float rightPixelDelta, float topPixelDelta, float bottomPixelDelta) pixelDelta)
        {
            (float leftNormalDelta, float rightNormalDelta, float topNormalDelta, float bottomNormalDelta) normalDelta =
                (pixelDelta.leftPixelDelta / Screen.width,
                 pixelDelta.rightPixelDelta / Screen.width,
                 pixelDelta.topPixelDelta / Screen.height,
                 pixelDelta.bottomPixelDelta / Screen.height);

            ShiftViewportBorderByNormal(viewport, normalDelta);
        }

        private void ShiftViewportBorderByNormal(
            Viewport viewport,
            (float leftNormalDelta, float rightNormalDelta, float topNormalDelta, float bottomNormalDelta) normalDelta)
        {
            viewport.ViewportRect = new Rect(
                viewport.ViewportRect.x + normalDelta.leftNormalDelta,
                viewport.ViewportRect.y + normalDelta.bottomNormalDelta,
                viewport.ViewportRect.width + normalDelta.rightNormalDelta - normalDelta.leftNormalDelta,
                viewport.ViewportRect.height + normalDelta.topNormalDelta - normalDelta.bottomNormalDelta);
        }

        public void PopulateViewports(ViewportLayout viewportLayout)
        {
            HideViewports();
            _viewports.Clear();
            foreach (var layout in viewportLayout.Layouts)
            {
                ViewportID viewportID = FindObjectsOfType<ViewportID>()
                    .FirstOrDefault(id => layout.ID.Equals(id.ID));

                if (viewportID == null)
                {
                    Debug.LogError("Unable to find ID listed in layout.");
                    return;
                }

                new Viewport(this, viewportID, layout.name, layout.ViewportRect, viewportLayout.Padding);
                ShowViewport(viewportID);
            }
        }

        private void ShowViewport(ViewportID viewportID)
        {
            if (viewportID == null) return;
            viewportID.ViewportCamera.targetDisplay = VIEWPORT_SHOW_DISPLAY;
        }

        private void ShowViewport(Viewport viewport) => ShowViewport(viewport.ViewportID);

        private void ShowViewports(List<Viewport> viewports)
        {
            if (viewports == null) return;
            foreach (Viewport viewport in viewports) ShowViewport(viewport);
        }

        private void ShowViewports() => ShowViewports(_viewports);

        private void HideViewport(ViewportID viewportID)
        {
            if (viewportID == null) return;
            viewportID.ViewportCamera.targetDisplay = VIEWPORT_HIDE_DISPLAY;
        }

        private void HideViewport(Viewport viewport) => HideViewport(viewport.ViewportID);

        private void HideViewports(List<Viewport> viewports)
        {
            if (viewports == null) return;
            foreach (Viewport viewport in viewports) HideViewport(viewport);
        }

        private void HideViewports() => HideViewports(_viewports);

        public void PopulateViewports() => PopulateViewports(_currentLayout);

        public void UpdateViewportLayout()
        {
            if (_currentLayout == null)
            {
                Debug.LogError("No Viewport Layout assigned.");
                return;
            }
            _currentLayout.UpdateLayoutFromViewports(_viewports);
        }

        private void PrintViewportData(Viewport viewport)
        {
            Debug.Log($"{viewport.name} viewport point: {_viewportPosition}");
            Debug.Log("viewport pixelRect: " + viewport.ViewportRect);
            Debug.Log($"viewport pixelposition: {new Vector2(viewport.PixelWidth * _viewportPosition.x, viewport.PixelHeight * _viewportPosition.y)}");
        }

        private bool IsNearCorner(Vector2 viewportPosition)
        {
            return Vector2.Distance(new Vector2(0, 0), viewportPosition) < _viewportBorderThreshold.magnitude ||
                   Vector2.Distance(new Vector2(1, 0), viewportPosition) < _viewportBorderThreshold.magnitude ||
                   Vector2.Distance(new Vector2(0, 1), viewportPosition) < _viewportBorderThreshold.magnitude ||
                   Vector2.Distance(new Vector2(1, 1), viewportPosition) < _viewportBorderThreshold.magnitude;
        }

        private bool HasMouseMoved() => _lastMousePosition != Input.mousePosition;

        public bool IsPointerInViewport(Vector2 pointerPosition, Viewport viewport)
        {
            var viewportRegionNormals = viewport.ScreenToCustomViewport(pointerPosition);
            return viewportRegionNormals.x >= 0 && viewportRegionNormals.x <= 1 &&
                   viewportRegionNormals.y >= 0 && viewportRegionNormals.y <= 1;
        }

        private bool IsPointerOverUI()
        {
            var es = EventSystem.current;
            if (es == null) return false;

            var data = new PointerEventData(es) { position = Input.mousePosition };
            var results = new List<RaycastResult>();
            es.RaycastAll(data, results);

            return results.Any(r =>
            {
                var c = r.gameObject.GetComponentInParent<Canvas>();
                return c != null && c.renderMode != RenderMode.WorldSpace;
            });
        }

        public List<Viewport> GetViewportsUnderPointer()
        {
            if (IsPointerOverUI())
                return new List<Viewport>();

            return _viewports
                .Where(v => v.ViewportID.ViewportCamera.targetDisplay == SHOW_DISPLAY
                            && IsPointerInViewport(Input.mousePosition, v))
                .ToList();
        }

        public Viewport GetTopmostViewportUnderPointer()
        {
            var hits = GetViewportsUnderPointer();
            if (hits.Count == 0) return null;
            return hits.OrderBy(v => v.ViewportID.ViewportCamera.depth).Last();
        }

        public bool CanInteract(Viewport vp) => vp != null && vp == GetTopmostViewportUnderPointer();

        public bool CanInteract(Camera cam)
        {
            var top = GetTopmostViewportUnderPointer();
            return top != null && top.ViewportID.ViewportCamera == cam;
        }

        private void EnableOnly(Viewport top)
        {
            foreach (var v in _viewports)
            {
                bool on = (v == top);
                var cam = v.ViewportID?.ViewportCamera;
                if (cam)
                {
                    var pr = cam.GetComponent<PhysicsRaycaster>();
                    if (pr) pr.enabled = on;
                }
                foreach (var gr in v.ViewportID.GetComponentsInChildren<GraphicRaycaster>(true))
                    gr.enabled = on;
            }
        }

        private void RestoreAll()
        {
            foreach (var v in _viewports)
            {
                var cam = v.ViewportID?.ViewportCamera;
                if (cam)
                {
                    var pr = cam.GetComponent<PhysicsRaycaster>();
                    if (pr) pr.enabled = true;
                }
                foreach (var gr in v.ViewportID.GetComponentsInChildren<GraphicRaycaster>(true))
                    gr.enabled = true;
            }
        }
    }
}
Editor is loading...
Leave a Comment