A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine.UIElements;
3using UnityEngine;
4
5namespace UnityEditor.Rendering.LookDev
6{
7 //TODO: clamps to always have both node on screen
8 class ComparisonGizmoController : Manipulator
9 {
10 const float k_DragPadding = 0.05f;
11 const float k_ReferenceScale = 1080f;
12
13 ComparisonGizmoState m_State;
14 SwitchableCameraController m_Switcher;
15
16 enum Selected
17 {
18 None,
19 NodeFirstView,
20 NodeSecondView,
21 PlaneSeparator,
22 Fader
23 }
24 Selected m_Selected;
25
26 Vector2 m_SavedRelativePositionOnMouseDown;
27 bool m_IsDragging;
28
29 bool isDragging
30 {
31 get => m_IsDragging;
32 set
33 {
34 //As in scene view, stop dragging as first button is release in case of multiple button down
35 if (value ^ m_IsDragging)
36 {
37 if (value)
38 {
39 target.RegisterCallback<MouseMoveEvent>(OnMouseDrag);
40 target.CaptureMouse();
41 }
42 else
43 {
44 target.ReleaseMouse();
45 target.UnregisterCallback<MouseMoveEvent>(OnMouseDrag);
46 }
47 m_IsDragging = value;
48 }
49 }
50 }
51
52 public ComparisonGizmoController(SwitchableCameraController switcher)
53 {
54 m_Switcher = switcher;
55 }
56
57 public void UpdateGizmoState(ComparisonGizmoState state)
58 {
59 m_State = state;
60 }
61
62 protected override void RegisterCallbacksOnTarget()
63 {
64 target.RegisterCallback<MouseDownEvent>(OnMouseDown);
65 target.RegisterCallback<MouseUpEvent>(OnMouseUp);
66 target.RegisterCallback<WheelEvent>(OnScrollWheel);
67 }
68
69 protected override void UnregisterCallbacksFromTarget()
70 {
71 target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
72 target.UnregisterCallback<MouseUpEvent>(OnMouseUp);
73 target.UnregisterCallback<WheelEvent>(OnScrollWheel);
74 }
75
76 void OnScrollWheel(WheelEvent evt)
77 {
78 if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit)
79 return;
80 if (GetViewFromComposition(evt.localMousePosition) == ViewIndex.Second)
81 m_Switcher.SwitchUntilNextWheelEvent();
82 //let event be propagated to views
83 }
84
85 void OnMouseDown(MouseDownEvent evt)
86 {
87 if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit)
88 return;
89
90 Rect displayRect = target.contentRect;
91 SelectGizmoZone(GetNormalizedCoordinates(evt.localMousePosition, displayRect));
92 if (m_Selected != Selected.None)
93 {
94 m_SavedRelativePositionOnMouseDown = GetNormalizedCoordinates(evt.localMousePosition, displayRect) - m_State.center;
95 isDragging = true;
96 //We do not want to move camera and gizmo at the same time.
97 evt.StopImmediatePropagation();
98 }
99 else
100 {
101 //else let event be propagated to views
102 if (GetViewFromComposition(evt.localMousePosition) == ViewIndex.Second)
103 m_Switcher.SwitchUntilNextEndOfDrag();
104 }
105 }
106
107 void OnMouseUp(MouseUpEvent evt)
108 {
109 if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit
110 || m_Selected == Selected.None)
111 return;
112
113 // deadzone in fader gizmo
114 if (m_Selected == Selected.Fader && Mathf.Abs(m_State.blendFactor) < ComparisonGizmoState.circleRadiusSelected / (m_State.length - ComparisonGizmoState.circleRadius))
115 m_State.blendFactor = 0f;
116
117 m_Selected = Selected.None;
118 isDragging = false;
119 //We do not want to move camera and gizmo at the same time.
120 evt.StopImmediatePropagation();
121
122 LookDev.SaveConfig();
123 }
124
125 void OnMouseDrag(MouseMoveEvent evt)
126 {
127 if (m_Selected == Selected.None)
128 return;
129
130 switch (m_Selected)
131 {
132 case Selected.PlaneSeparator: OnDragPlaneSeparator(evt); break;
133 case Selected.NodeFirstView:
134 case Selected.NodeSecondView: OnDragPlaneNodeExtremity(evt); break;
135 case Selected.Fader: OnDragFader(evt); break;
136 default: throw new ArgumentException("Unknown kind of Selected");
137 }
138 }
139
140 void OnDragPlaneSeparator(MouseMoveEvent evt)
141 {
142 //TODO: handle case when resizing window (clamping)
143 Vector2 newPosition = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect) - m_SavedRelativePositionOnMouseDown;
144
145 // We clamp the center of the gizmo to the border of the screen in order to avoid being able to put it out of the screen.
146 // The safe band is here to ensure that you always see at least part of the gizmo in order to be able to grab it again.
147 //Vector2 extends = GetNormalizedCoordinates(new Vector2(displayRect.width, displayRect.height), displayRect);
148 //newPosition.x = Mathf.Clamp(newPosition.x, -extends.x + k_DragPadding, extends.x - k_DragPadding);
149 //newPosition.y = Mathf.Clamp(newPosition.y, -extends.y + k_DragPadding, extends.y - k_DragPadding);
150
151 m_State.Update(newPosition, m_State.length, m_State.angle);
152 //We do not want to move camera and gizmo at the same time.
153 evt.StopImmediatePropagation();
154 }
155
156 void OnDragPlaneNodeExtremity(MouseMoveEvent evt)
157 {
158 Vector2 normalizedCoord = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect);
159 Vector2 basePoint, newPoint;
160 float angleSnapping = Mathf.Deg2Rad * 45.0f * 0.5f;
161
162 newPoint = normalizedCoord;
163 basePoint = m_Selected == Selected.NodeFirstView ? m_State.point2 : m_State.point1;
164
165 // Snap to a multiple of "angleSnapping"
166 if ((evt.modifiers & EventModifiers.Shift) != 0)
167 {
168 Vector3 verticalPlane = new Vector3(-1.0f, 0.0f, basePoint.x);
169 float side = Vector3.Dot(new Vector3(normalizedCoord.x, normalizedCoord.y, 1.0f), verticalPlane);
170
171 float angle = Mathf.Deg2Rad * Vector2.Angle(new Vector2(0.0f, 1.0f), normalizedCoord - basePoint);
172 if (side > 0.0f)
173 angle = 2.0f * Mathf.PI - angle;
174 angle = (int)(angle / angleSnapping) * angleSnapping;
175 Vector2 dir = normalizedCoord - basePoint;
176 float length = dir.magnitude;
177 newPoint = basePoint + new Vector2(Mathf.Sin(angle), Mathf.Cos(angle)) * length;
178 }
179
180 if (m_Selected == Selected.NodeFirstView)
181 m_State.Update(newPoint, basePoint);
182 else
183 m_State.Update(basePoint, newPoint);
184 //We do not want to move camera and gizmo at the same time.
185 evt.StopImmediatePropagation();
186 }
187
188 void OnDragFader(MouseMoveEvent evt)
189 {
190 Vector2 mousePosition = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect);
191 float distanceToOrthoPlane = -Vector3.Dot(new Vector3(mousePosition.x, mousePosition.y, 1.0f), m_State.planeOrtho) / m_State.blendFactorMaxGizmoDistance;
192 m_State.blendFactor = Mathf.Clamp(distanceToOrthoPlane, -1.0f, 1.0f);
193 //We do not want to move camera and gizmo at the same time.
194 evt.StopImmediatePropagation();
195 }
196
197 void SelectGizmoZone(Vector2 normalizedMousePosition)
198 {
199 //TODO: Optimize
200 Vector3 normalizedMousePositionZ1 = new Vector3(normalizedMousePosition.x, normalizedMousePosition.y, 1.0f);
201 float distanceToPlane = Vector3.Dot(normalizedMousePositionZ1, m_State.plane);
202 float absDistanceToPlane = Mathf.Abs(distanceToPlane);
203 float distanceFromCenter = Vector2.Distance(normalizedMousePosition, m_State.center);
204 float distanceToOrtho = Vector3.Dot(normalizedMousePositionZ1, m_State.planeOrtho);
205 float side = (distanceToOrtho > 0.0f) ? 1.0f : -1.0f;
206 Vector2 orthoPlaneNormal = new Vector2(m_State.planeOrtho.x, m_State.planeOrtho.y);
207
208 Selected selected = Selected.None;
209 if (absDistanceToPlane < ComparisonGizmoState.circleRadiusSelected && (distanceFromCenter < (m_State.length + ComparisonGizmoState.circleRadiusSelected)))
210 {
211 if (absDistanceToPlane < ComparisonGizmoState.thicknessSelected)
212 selected = Selected.PlaneSeparator;
213
214 Vector2 circleCenter = m_State.center + side * orthoPlaneNormal * m_State.length;
215 float d = Vector2.Distance(normalizedMousePosition, circleCenter);
216 if (d <= ComparisonGizmoState.circleRadiusSelected)
217 selected = side > 0.0f ? Selected.NodeFirstView : Selected.NodeSecondView;
218
219 float maxBlendCircleDistanceToCenter = m_State.blendFactorMaxGizmoDistance;
220 float blendCircleDistanceToCenter = m_State.blendFactor * maxBlendCircleDistanceToCenter;
221 Vector2 blendCircleCenter = m_State.center - orthoPlaneNormal * blendCircleDistanceToCenter;
222 float blendCircleSelectionRadius = Mathf.Lerp(ComparisonGizmoState.blendFactorCircleRadius, ComparisonGizmoState.blendFactorCircleRadiusSelected, Mathf.Clamp((maxBlendCircleDistanceToCenter - Mathf.Abs(blendCircleDistanceToCenter)) / (ComparisonGizmoState.blendFactorCircleRadiusSelected - ComparisonGizmoState.blendFactorCircleRadius), 0.0f, 1.0f));
223 if ((normalizedMousePosition - blendCircleCenter).magnitude < blendCircleSelectionRadius)
224 selected = Selected.Fader;
225 }
226
227 m_Selected = selected;
228 }
229
230 //normalize in [-1,1]^2 for a 1080^2. Can be above 1 for higher than 1080.
231 internal static Vector2 GetNormalizedCoordinates(Vector2 localMousePosition, Rect rect)
232 => new Vector2(
233 (2f * localMousePosition.x - rect.width) / k_ReferenceScale,
234 (-2f * localMousePosition.y + rect.height) / k_ReferenceScale);
235
236 ViewIndex GetViewFromComposition(Vector2 localCoordinate)
237 {
238 Vector2 normalizedLocalCoordinate = GetNormalizedCoordinates(localCoordinate, target.contentRect);
239 return Vector3.Dot(new Vector3(normalizedLocalCoordinate.x, normalizedLocalCoordinate.y, 1), m_State.plane) >= 0
240 ? ViewIndex.First
241 : ViewIndex.Second;
242 }
243 }
244}