forked from Unity-Technologies/UnityCsReference
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScrollView.cs
More file actions
370 lines (317 loc) · 15.5 KB
/
ScrollView.cs
File metadata and controls
370 lines (317 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
namespace UnityEngine.UIElements
{
// Assuming a ScrollView parent with a flex-direction column.
// The modes follow these rules :
//
// Vertical
// ---------------------
// Require elements with an height, width will stretch.
// If the ScrollView parent is set to flex-direction row the elements height will not stretch.
// How measure works :
// Width is restricted, height is not. content-container is set to overflow: scroll
//
// Horizontal
// ---------------------
// Require elements with a width. If ScrollView is set to flex-grow elements height stretch else they require a height.
// If the ScrollView parent is set to flex-direction row the elements height will stretch.
// How measure works :
// Height is restricted, width is not. content-container is set to overflow: scroll
//
// VerticalAndHorizontal
// ---------------------
// Require elements with an height, width will stretch.
// The difference with the Vertical type is that content will not wrap (white-space has no effect).
// How measure works :
// Nothing is restricted, the content-container will stop shrinking so that all the content fit and scrollers will appear.
// To achieve this content-viewport is set to overflow: scroll and flex-direction: row.
// content-container is set to flex-direction: column, flex-grow: 1 and align-self:flex-start.
//
// This type is more tricky, it requires the content-viewport and content-container to have a different flex-direction.
// "flex-grow:1" is to make elements stretch horizontally.
// "align-self:flex-start" prevent the content-container from shrinking below the content size vertically.
// "overflow:scroll" on the content-viewport and content-container is to not restrict measured elements in any direction.
public enum ScrollViewMode
{
Vertical,
Horizontal,
VerticalAndHorizontal
}
public class ScrollView : VisualElement
{
public new class UxmlFactory : UxmlFactory<ScrollView, UxmlTraits> {}
public new class UxmlTraits : VisualElement.UxmlTraits
{
UxmlEnumAttributeDescription<ScrollViewMode> m_ScrollViewMode = new UxmlEnumAttributeDescription<ScrollViewMode> { name = "mode", defaultValue = ScrollViewMode.Vertical};
UxmlBoolAttributeDescription m_ShowHorizontal = new UxmlBoolAttributeDescription { name = "show-horizontal-scroller" };
UxmlBoolAttributeDescription m_ShowVertical = new UxmlBoolAttributeDescription { name = "show-vertical-scroller" };
UxmlFloatAttributeDescription m_HorizontalPageSize = new UxmlFloatAttributeDescription { name = "horizontal-page-size", defaultValue = Scroller.kDefaultPageSize };
UxmlFloatAttributeDescription m_VerticalPageSize = new UxmlFloatAttributeDescription { name = "vertical-page-size", defaultValue = Scroller.kDefaultPageSize };
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
ScrollView scrollView = (ScrollView)ve;
scrollView.SetScrollViewMode(m_ScrollViewMode.GetValueFromBag(bag, cc));
scrollView.showHorizontal = m_ShowHorizontal.GetValueFromBag(bag, cc);
scrollView.showVertical = m_ShowVertical.GetValueFromBag(bag, cc);
scrollView.horizontalPageSize = m_HorizontalPageSize.GetValueFromBag(bag, cc);
scrollView.verticalPageSize = m_VerticalPageSize.GetValueFromBag(bag, cc);
}
}
private bool m_ShowHorizontal;
public bool showHorizontal
{
get { return m_ShowHorizontal;}
set
{
m_ShowHorizontal = value;
UpdateScrollers(m_ShowHorizontal, m_ShowVertical);
}
}
private bool m_ShowVertical;
public bool showVertical
{
get { return m_ShowVertical;}
set
{
m_ShowVertical = value;
UpdateScrollers(m_ShowHorizontal, m_ShowVertical);
}
}
internal bool needsHorizontal
{
get { return showHorizontal || (contentContainer.layout.width - layout.width > 0); }
}
internal bool needsVertical
{
get { return showVertical || (contentContainer.layout.height - layout.height > 0); }
}
public Vector2 scrollOffset
{
get { return new Vector2(horizontalScroller.value, verticalScroller.value); }
set
{
if (value != scrollOffset)
{
horizontalScroller.value = value.x;
verticalScroller.value = value.y;
UpdateContentViewTransform();
}
}
}
public float horizontalPageSize
{
get { return horizontalScroller.slider.pageSize; }
set { horizontalScroller.slider.pageSize = value; }
}
public float verticalPageSize
{
get { return verticalScroller.slider.pageSize; }
set { verticalScroller.slider.pageSize = value; }
}
private float scrollableWidth
{
get { return contentContainer.layout.width - contentViewport.layout.width; }
}
private float scrollableHeight
{
get { return contentContainer.layout.height - contentViewport.layout.height; }
}
void UpdateContentViewTransform()
{
// Adjust contentContainer's position
var t = contentContainer.transform.position;
var offset = scrollOffset;
t.x = GUIUtility.RoundToPixelGrid(-offset.x);
t.y = GUIUtility.RoundToPixelGrid(-offset.y);
contentContainer.transform.position = t;
// TODO: Can we get rid of this?
this.IncrementVersion(VersionChangeType.Repaint);
}
public void ScrollTo(VisualElement child)
{
if (child == null)
{
throw new ArgumentNullException(nameof(child));
}
// Child not in content view, no need to continue.
if (!contentContainer.Contains(child))
throw new ArgumentException("Cannot scroll to null child");
float yTransform = contentContainer.transform.position.y * -1;
float viewMin = contentViewport.layout.yMin + yTransform;
float viewMax = contentViewport.layout.yMax + yTransform;
float childBoundaryMin = child.layout.yMin;
float childBoundaryMax = child.layout.yMax;
if ((childBoundaryMin >= viewMin && childBoundaryMax <= viewMax) || float.IsNaN(childBoundaryMin) || float.IsNaN(childBoundaryMax))
return;
bool scrollUpward = false;
float deltaDistance = childBoundaryMax - viewMax;
if (deltaDistance < -1)
{
// Direction = upward
deltaDistance = viewMin - childBoundaryMin;
scrollUpward = true;
}
float deltaOffset = deltaDistance * verticalScroller.highValue / scrollableHeight;
verticalScroller.value = scrollOffset.y + (scrollUpward ? -deltaOffset : deltaOffset);
UpdateContentViewTransform();
}
public VisualElement contentViewport { get; private set; } // Represents the visible part of contentContainer
public Scroller horizontalScroller { get; private set; }
public Scroller verticalScroller { get; private set; }
private VisualElement m_ContentContainer;
public override VisualElement contentContainer // Contains full content, potentially partially visible
{
get { return m_ContentContainer; }
}
public static readonly string ussClassName = "unity-scroll-view";
public static readonly string viewportUssClassName = ussClassName + "__content-viewport";
public static readonly string contentUssClassName = ussClassName + "__content-container";
public static readonly string hScrollerUssClassName = ussClassName + "__horizontal-scroller";
public static readonly string vScrollerUssClassName = ussClassName + "__vertical-scroller";
public static readonly string horizontalVariantUssClassName = ussClassName + "--horizontal";
public static readonly string verticalVariantUssClassName = ussClassName + "--vertical";
public static readonly string verticalHorizontalVariantUssClassName = ussClassName + "--vertical-horizontal";
public static readonly string scrollVariantUssClassName = ussClassName + "--scroll";
public ScrollView() : this(ScrollViewMode.Vertical) {}
public ScrollView(ScrollViewMode scrollViewMode)
{
AddToClassList(ussClassName);
contentViewport = new VisualElement() { name = "unity-content-viewport" };
contentViewport.AddToClassList(viewportUssClassName);
contentViewport.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
hierarchy.Add(contentViewport);
m_ContentContainer = new VisualElement() { name = "unity-content-container" };
m_ContentContainer.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
m_ContentContainer.AddToClassList(contentUssClassName);
m_ContentContainer.usageHints = UsageHints.GroupTransform;
contentViewport.Add(m_ContentContainer);
SetScrollViewMode(scrollViewMode);
const int defaultMinScrollValue = 0;
const int defaultMaxScrollValue = 100;
horizontalScroller = new Scroller(defaultMinScrollValue, defaultMaxScrollValue,
(value) =>
{
scrollOffset = new Vector2(value, scrollOffset.y);
UpdateContentViewTransform();
}, SliderDirection.Horizontal)
{ viewDataKey = "HorizontalScroller", visible = false };
horizontalScroller.AddToClassList(hScrollerUssClassName);
hierarchy.Add(horizontalScroller);
verticalScroller = new Scroller(defaultMinScrollValue, defaultMaxScrollValue,
(value) =>
{
scrollOffset = new Vector2(scrollOffset.x, value);
UpdateContentViewTransform();
}, SliderDirection.Vertical)
{ viewDataKey = "VerticalScroller", visible = false };
verticalScroller.AddToClassList(vScrollerUssClassName);
hierarchy.Add(verticalScroller);
RegisterCallback<WheelEvent>(OnScrollWheel);
scrollOffset = Vector2.zero;
}
internal void SetScrollViewMode(ScrollViewMode scrollViewMode)
{
RemoveFromClassList(verticalVariantUssClassName);
RemoveFromClassList(horizontalVariantUssClassName);
RemoveFromClassList(verticalHorizontalVariantUssClassName);
RemoveFromClassList(scrollVariantUssClassName);
switch (scrollViewMode)
{
case ScrollViewMode.Vertical:
AddToClassList(verticalVariantUssClassName);
AddToClassList(scrollVariantUssClassName);
break;
case ScrollViewMode.Horizontal:
AddToClassList(horizontalVariantUssClassName);
AddToClassList(scrollVariantUssClassName);
break;
case ScrollViewMode.VerticalAndHorizontal:
AddToClassList(scrollVariantUssClassName);
AddToClassList(verticalHorizontalVariantUssClassName);
break;
}
}
private void OnGeometryChanged(GeometryChangedEvent evt)
{
// Only affected by dimension changes
if (evt.oldRect.size == evt.newRect.size)
{
return;
}
// Get the initial information on the necessity of the scrollbars
bool needsVerticalCached = needsVertical;
bool needsHorizontalCached = needsHorizontal;
// Here, we allow the removal of the scrollbar only in the first layout pass.
// Addition is always allowed.
if (evt.layoutPass > 0)
{
needsVerticalCached = needsVerticalCached || verticalScroller.visible;
needsHorizontalCached = needsHorizontalCached || horizontalScroller.visible;
}
UpdateScrollers(needsHorizontalCached, needsVerticalCached);
UpdateContentViewTransform();
}
void UpdateScrollers(bool displayHorizontal, bool displayVertical)
{
if (contentContainer.layout.width > Mathf.Epsilon)
horizontalScroller.Adjust(contentViewport.layout.width / contentContainer.layout.width);
if (contentContainer.layout.height > Mathf.Epsilon)
verticalScroller.Adjust(contentViewport.layout.height / contentContainer.layout.height);
// Set availability
horizontalScroller.SetEnabled(contentContainer.layout.width - contentViewport.layout.width > 0);
verticalScroller.SetEnabled(contentContainer.layout.height - contentViewport.layout.height > 0);
// Expand content if scrollbars are hidden
contentViewport.style.marginRight = displayVertical ? verticalScroller.layout.width : 0;
horizontalScroller.style.right = displayVertical ? verticalScroller.layout.width : 0;
contentViewport.style.marginBottom = displayHorizontal ? horizontalScroller.layout.height : 0;
verticalScroller.style.bottom = displayHorizontal ? horizontalScroller.layout.height : 0;
if (displayHorizontal && scrollableWidth > 0f)
{
horizontalScroller.lowValue = 0f;
horizontalScroller.highValue = scrollableWidth;
}
else
{
horizontalScroller.value = 0f;
}
if (displayVertical && scrollableHeight > 0f)
{
verticalScroller.lowValue = 0f;
verticalScroller.highValue = scrollableHeight;
}
else
{
verticalScroller.value = 0f;
}
// Set visibility and remove/add content viewport margin as necessary
if (horizontalScroller.visible != displayHorizontal)
{
horizontalScroller.visible = displayHorizontal;
}
if (verticalScroller.visible != displayVertical)
{
verticalScroller.visible = displayVertical;
}
}
// TODO: Same behaviour as IMGUI Scroll view
void OnScrollWheel(WheelEvent evt)
{
var oldValue = verticalScroller.value;
if (contentContainer.layout.height - layout.height > 0)
{
if (evt.delta.y < 0)
verticalScroller.ScrollPageUp(Mathf.Abs(evt.delta.y));
else if (evt.delta.y > 0)
verticalScroller.ScrollPageDown(Mathf.Abs(evt.delta.y));
}
if (verticalScroller.value != oldValue)
{
evt.StopPropagation();
}
}
}
}