using UnityEngine; using UnityEditor; using UnityEngine.UIElements; namespace Unity.Cinemachine.Editor { /// /// Use an instance of this class to draw screen composer guides in the game view. /// This is an internal class, and is not meant to be called outside of Cinemachine. /// class GameViewComposerGuides { /// Delegate for getting the hard/soft guide rects /// The Hard/Soft guide rect public delegate ScreenComposerSettings CompositionGetter(); /// Delegate for setting the hard/soft guide rects /// The value to set public delegate void CompositionSetter(ScreenComposerSettings s); /// Delegate to get the current object whose guides are being drawn /// The target object whose guides are being drawn public delegate SerializedObject ObjectGetter(); /// Get the Composition settings. Client must implement this public CompositionGetter GetComposition; /// Get the Composition settings. Client must implement this public CompositionSetter SetComposition; /// Get the target object whose guides are being drawn. Client must implement this public ObjectGetter Target; // This is necessary because we don't get mouse events in the game view in Edit mode. // We need to trigger repaint when the mouse moves over the window. class GameViewEventCatcher { class Dragger { readonly VisualElement m_Root; void OnMouseMove(MouseMoveEvent e) { if (m_Root.panel != null && !Application.isPlaying && CinemachineCorePrefs.ShowInGameGuides.Value && CinemachineCorePrefs.DraggableComposerGuides.Value && CinemachineCore.SoloCamera == null) { InspectorUtility.RepaintGameView(); } } public Dragger(VisualElement root) { m_Root = root; if (m_Root == null || m_Root.panel == null || m_Root.panel.visualTree == null) return; m_Root.panel.visualTree.RegisterCallback(OnMouseMove, TrickleDown.TrickleDown); } public void Unregister() { if (m_Root == null || m_Root.panel == null || m_Root.panel.visualTree == null) return; m_Root.panel.visualTree.UnregisterCallback(OnMouseMove, TrickleDown.TrickleDown); } } // One for each game view Dragger[] m_Draggers; // Create manipulator in each game view public void OnEnable() { System.Reflection.Assembly assembly = typeof(EditorWindow).Assembly; System.Type type = assembly.GetType("UnityEditor.GameView"); var gameViews = Resources.FindObjectsOfTypeAll(type); m_Draggers = new Dragger[gameViews.Length]; for (int i = 0; i < gameViews.Length; ++i) { var gameViewRoot = (gameViews[i] as EditorWindow).rootVisualElement; m_Draggers[i] = new Dragger(gameViewRoot); } } public void OnDisable() { for (int i = 0; m_Draggers != null && i < m_Draggers.Length; ++i) { var dragger = m_Draggers[i]; if (dragger != null) dragger.Unregister(); } m_Draggers = null; } } // For dragging the bars - order defines precedence enum DragBar { Center, HardRight, HardBottom, HardLeft, HardTop, DeadRight, DeadBottom, DeadLeft, DeadTop, NONE }; DragBar m_IsDragging = DragBar.NONE; DragBar m_IsHot = DragBar.NONE; Rect[] m_DragBars = new Rect[9]; GameViewEventCatcher m_EventCatcher = new (); // Call this from inspector's OnEnable() public void OnEnable() => m_EventCatcher.OnEnable(); // Call this from inspector's OnDisable() public void OnDisable() => m_EventCatcher.OnDisable(); Rect GetCameraRect(Camera outputCamera, LensSettings lens) { Rect cameraRect = outputCamera.pixelRect; float screenHeight = cameraRect.height; float screenWidth = cameraRect.width; float screenAspect = screenWidth / screenHeight; switch (outputCamera.gateFit) { case Camera.GateFitMode.Vertical: screenWidth = screenHeight * lens.Aspect; cameraRect.position += new Vector2((cameraRect.width - screenWidth) * 0.5f, 0); break; case Camera.GateFitMode.Horizontal: screenHeight = screenWidth / lens.Aspect; cameraRect.position += new Vector2(0, (cameraRect.height - screenHeight) * 0.5f); break; case Camera.GateFitMode.Overscan: if (screenAspect < lens.Aspect) { screenHeight = screenWidth / lens.Aspect; cameraRect.position += new Vector2(0, (cameraRect.height - screenHeight) * 0.5f); } else { screenWidth = screenHeight * lens.Aspect; cameraRect.position += new Vector2((cameraRect.width - screenWidth) * 0.5f, 0); } break; case Camera.GateFitMode.Fill: if (screenAspect > lens.Aspect) { screenHeight = screenWidth / lens.Aspect; cameraRect.position += new Vector2(0, (cameraRect.height - screenHeight) * 0.5f); } else { screenWidth = screenHeight * lens.Aspect; cameraRect.position += new Vector2((cameraRect.width - screenWidth) * 0.5f, 0); } break; case Camera.GateFitMode.None: break; } cameraRect = new Rect(cameraRect.position, new Vector2(screenWidth, screenHeight)); // Invert Y float h = cameraRect.height; cameraRect.yMax = Screen.height - cameraRect.yMin; cameraRect.yMin = cameraRect.yMax - h; // Shift the guides along with the lens if (lens.IsPhysicalCamera) cameraRect.position += new Vector2( -screenWidth * lens.PhysicalProperties.LensShift.x, screenHeight * lens.PhysicalProperties.LensShift.y); return cameraRect; } /// /// Call this from the inspector's OnGUI. Draws the guides and manages dragging. /// /// Is the target live /// Destination camera /// Current lens settings /// True if hard guides should be shown public void OnGUI_DrawGuides(bool isLive, Camera outputCamera, LensSettings lens) { var cameraRect = GetCameraRect(outputCamera, lens); // Rotate the guides along with the dutch Matrix4x4 oldMatrix = GUI.matrix; GUI.matrix = Matrix4x4.Translate(cameraRect.min); GUIUtility.RotateAroundPivot(lens.Dutch, cameraRect.center); // Desaturate colors if not live came Color hardBarsColour = CinemachineComposerPrefs.HardBoundsOverlayColour.Value; Color softBarsColour = CinemachineComposerPrefs.SoftBoundsOverlayColour.Value; float overlayOpacity = CinemachineComposerPrefs.OverlayOpacity.Value; if (!isLive) { softBarsColour = CinemachineCorePrefs.InactiveGizmoColour.Value; hardBarsColour = Color.Lerp(softBarsColour, Color.black, 0.5f); overlayOpacity /= 2; } hardBarsColour.a *= overlayOpacity; softBarsColour.a *= overlayOpacity; var composition = GetComposition(); var hard = composition.HardLimitsRect; hard = new (hard.xMin * cameraRect.width, hard.yMin * cameraRect.height, hard.width * cameraRect.width, hard.height * cameraRect.height); const float kBarSize = 2; m_DragBars[(int)DragBar.HardLeft] = new Rect(hard.xMin, 0f, 0, cameraRect.height).Inflated(new Vector2(kBarSize, 0)); m_DragBars[(int)DragBar.HardTop] = new Rect(0f, hard.yMin, cameraRect.width, 0).Inflated(new Vector2(0, kBarSize)); m_DragBars[(int)DragBar.HardRight] = new Rect(hard.xMax, 0f, 0, cameraRect.height).Inflated(new Vector2(kBarSize, 0)); m_DragBars[(int)DragBar.HardBottom] = new Rect(0f, hard.yMax, cameraRect.width, 0).Inflated(new Vector2(0, kBarSize)); var dead = composition.DeadZoneRect; dead = new (dead.xMin * cameraRect.width, dead.yMin * cameraRect.height, dead.width * cameraRect.width, dead.height * cameraRect.height); m_DragBars[(int)DragBar.DeadLeft] = new Rect(dead.xMin, 0f, 0, cameraRect.height).Inflated(new Vector2(kBarSize, 0)); m_DragBars[(int)DragBar.DeadTop] = new Rect(0f, dead.yMin, cameraRect.width, 0).Inflated(new Vector2(0, kBarSize)); m_DragBars[(int)DragBar.DeadRight] = new Rect(dead.xMax, 0f, 0, cameraRect.height).Inflated(new Vector2(kBarSize, 0)); m_DragBars[(int)DragBar.DeadBottom] = new Rect(0f, dead.yMax, cameraRect.width, 0).Inflated(new Vector2(0, kBarSize)); m_DragBars[(int)DragBar.Center] = new Rect(dead.xMin, dead.yMin, dead.xMax - dead.xMin, dead.yMax - dead.yMin); // Handle dragging bars if (CinemachineCorePrefs.DraggableComposerGuides.Value && isLive) OnGuiHandleBarDragging(cameraRect.width, cameraRect.height, ref composition); // Draw the masks var tex = Texture2D.whiteTexture; var oldColor = GUI.color; if (composition.HardLimits.Enabled) { GUI.color = hardBarsColour; var left = new Rect(0, hard.yMin, Mathf.Max(0, hard.xMin), hard.height); var right = new Rect(hard.xMax, hard.yMin, Mathf.Max(0, cameraRect.width - hard.xMax), hard.height); var top = new Rect(Mathf.Min(0, hard.xMin), 0, Mathf.Max(cameraRect.width, hard.xMax) - Mathf.Min(0, hard.xMin), Mathf.Max(0, hard.yMin)); var bottom = new Rect(Mathf.Min(0, hard.xMin), hard.yMax, Mathf.Max(cameraRect.width, hard.xMax) - Mathf.Min(0, hard.xMin), Mathf.Max(0, cameraRect.height - hard.yMax)); GUI.DrawTexture(left, tex, ScaleMode.StretchToFill); GUI.DrawTexture(top, tex, ScaleMode.StretchToFill); GUI.DrawTexture(right, tex, ScaleMode.StretchToFill); GUI.DrawTexture(bottom, tex, ScaleMode.StretchToFill); } GUI.color = softBarsColour; if (composition.DeadZone.Enabled) { var left = new Rect(hard.xMin, dead.yMin, dead.xMin - hard.xMin, dead.height); var top = new Rect(hard.xMin, hard.yMin, hard.xMax - hard.xMin, dead.yMin - hard.yMin); var right = new Rect(dead.xMax, dead.yMin, hard.xMax - dead.xMax, dead.yMax - dead.yMin); var bottom = new Rect(hard.xMin, dead.yMax, hard.xMax - hard.xMin, hard.yMax - dead.yMax); GUI.DrawTexture(left, tex, ScaleMode.StretchToFill); GUI.DrawTexture(top, tex, ScaleMode.StretchToFill); GUI.DrawTexture(right, tex, ScaleMode.StretchToFill); GUI.DrawTexture(bottom, tex, ScaleMode.StretchToFill); } // Draw the drag bars GUI.DrawTexture(m_DragBars[(int)DragBar.DeadLeft], tex, ScaleMode.StretchToFill); GUI.DrawTexture(m_DragBars[(int)DragBar.DeadTop], tex, ScaleMode.StretchToFill); GUI.DrawTexture(m_DragBars[(int)DragBar.DeadRight], tex, ScaleMode.StretchToFill); GUI.DrawTexture(m_DragBars[(int)DragBar.DeadBottom], tex, ScaleMode.StretchToFill); if (composition.HardLimits.Enabled) { GUI.color = hardBarsColour; GUI.DrawTexture(m_DragBars[(int)DragBar.HardLeft], tex, ScaleMode.StretchToFill); GUI.DrawTexture(m_DragBars[(int)DragBar.HardTop], tex, ScaleMode.StretchToFill); GUI.DrawTexture(m_DragBars[(int)DragBar.HardRight], tex, ScaleMode.StretchToFill); GUI.DrawTexture(m_DragBars[(int)DragBar.HardBottom], tex, ScaleMode.StretchToFill); } // Highlight the hot one if (m_IsHot != DragBar.NONE) { GUI.color = new Color(0.5f, 1, 1, 0.2f); var k = m_IsHot == DragBar.Center ? 10 : 2; GUI.DrawTexture(m_DragBars[(int)m_IsHot].Inflated(new Vector2(k, k)), tex, ScaleMode.StretchToFill); } GUI.matrix = oldMatrix; GUI.color = oldColor; } void OnGuiHandleBarDragging(float screenWidth, float screenHeight, ref ScreenComposerSettings composition) { if (Event.current.type == EventType.MouseUp) m_IsDragging = m_IsHot = DragBar.NONE; if (Event.current.type == EventType.MouseDown) m_IsDragging = GetDragBarUnderPoint(Event.current.mousePosition); if (Event.current.type == EventType.Repaint) m_IsHot = m_IsDragging != DragBar.NONE ? m_IsDragging : GetDragBarUnderPoint(Event.current.mousePosition); // Handle an actual drag event if (m_IsDragging != DragBar.NONE && Event.current.type == EventType.MouseDrag) { var d = new Vector2(Event.current.delta.x / screenWidth, Event.current.delta.y / screenHeight); var hard = composition.HardLimitsRect; var dead = composition.DeadZoneRect; switch (m_IsDragging) { case DragBar.Center: dead.position += d; break; case DragBar.DeadLeft: { if (composition.DeadZone.Enabled) dead = dead.Inflated(new Vector2(-d.x, 0)); else dead.position += new Vector2(d.x, 0); break; } case DragBar.DeadRight: { if (composition.DeadZone.Enabled) dead = dead.Inflated(new Vector2(d.x, 0)); else dead.position += new Vector2(d.x, 0); break; } case DragBar.DeadTop: { if (composition.DeadZone.Enabled) dead = dead.Inflated(new Vector2(0, -d.y)); else dead.position += new Vector2(0, d.y); break; } case DragBar.DeadBottom: { if (composition.DeadZone.Enabled) dead = dead.Inflated(new Vector2(0, d.y)); else dead.position += new Vector2(0, d.y); break; } case DragBar.HardLeft: hard = hard.Inflated(new Vector2(-d.x, 0)); break; case DragBar.HardRight: hard = hard.Inflated(new Vector2(d.x, 0)); break; case DragBar.HardBottom: hard = hard.Inflated(new Vector2(0, d.y)); break; case DragBar.HardTop: hard = hard.Inflated(new Vector2(0, -d.y)); break; } // Apply the changes, enforcing the bounds SetNewBounds(hard, dead, ref composition); Event.current.Use(); } } DragBar GetDragBarUnderPoint(Vector2 point) { var bar = DragBar.NONE; for (DragBar i = DragBar.Center; i < DragBar.NONE && bar == DragBar.NONE; ++i) { var slop = new Vector2(5f, 5f); if (i == DragBar.Center) { if (m_DragBars[(int)i].width > 3f * slop.x) slop.x = -slop.x; if (m_DragBars[(int)i].height > 3f * slop.y) slop.y = -slop.y; } var r = m_DragBars[(int)i].Inflated(slop); if (r.Contains(point)) bar = i; } return bar; } /// /// Helper to set the appropriate new rects in the target object, if something changed. /// void SetNewBounds(Rect hard, Rect dead, ref ScreenComposerSettings composition) { var oldHard = composition.HardLimitsRect; var oldDead = composition.DeadZoneRect; if (oldDead != dead || oldHard != hard) { Undo.RecordObject(Target().targetObject, "Composer Bounds"); var c = GetComposition(); if (oldDead != dead) c.DeadZoneRect = dead; if (oldHard != hard) c.HardLimitsRect = hard; SetComposition(c); Target().ApplyModifiedProperties(); } } } }