bm-core-ui 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1624 -0
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/build/@types/index.d.ts +14078 -0
- package/build/BMCodeEditor/BMCodeEditor.js +707 -0
- package/build/BMCollectionView/BMCollectionView.js +5861 -0
- package/build/BMCollectionView/BMCollectionViewCell.js +688 -0
- package/build/BMCollectionView/BMCollectionViewFlowLayout.js +4467 -0
- package/build/BMCollectionView/BMCollectionViewLayout.js +830 -0
- package/build/BMCollectionView/BMCollectionViewLayoutAttributes.js +673 -0
- package/build/BMCollectionView/BMCollectionViewMasonryLayout.js +491 -0
- package/build/BMCollectionView/BMCollectionViewStackLayout.js +634 -0
- package/build/BMCollectionView/BMCollectionViewTileLayout.js +1121 -0
- package/build/BMCoreUI.css +2833 -0
- package/build/BMView/BMAttributedLabelView.js +310 -0
- package/build/BMView/BMLayoutConstraint_v2.5.js +1813 -0
- package/build/BMView/BMLayoutGuide.js +192 -0
- package/build/BMView/BMLayoutSizeClass.js +466 -0
- package/build/BMView/BMMenu.js +574 -0
- package/build/BMView/BMScrollView.js +247 -0
- package/build/BMView/BMTextField.js +512 -0
- package/build/BMView/BMTextFieldDelegate.js +71 -0
- package/build/BMView/BMView_v2.5.js +3572 -0
- package/build/BMView/BMViewport.js +221 -0
- package/build/BMViewLayoutEditor/BMLayoutEditor.js +6117 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorConstraintSettings.js +417 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorDelegate.js +59 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingCells.js +1653 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettings.js +1480 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingsComplexCells.js +431 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingsDelegate.js +46 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorVariablesController.js +460 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorViewGroupSettings.js +293 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorViewSettings.js +382 -0
- package/build/BMViewLayoutEditor/BMLayoutVariableProvider.js +202 -0
- package/build/BMWindow/BMConfirmationPopup.js +477 -0
- package/build/BMWindow/BMKeyboardShortcut.js +151 -0
- package/build/BMWindow/BMPopover/BMPopover.js +492 -0
- package/build/BMWindow/BMToolWindow.js +71 -0
- package/build/BMWindow/BMWindow.js +2000 -0
- package/build/Core/BMAnimationContext.js +1181 -0
- package/build/Core/BMColor.js +991 -0
- package/build/Core/BMCoreUI.js +470 -0
- package/build/Core/BMFunctionCollection.js +110 -0
- package/build/Core/BMIndexPath.js +165 -0
- package/build/Core/BMInset.js +138 -0
- package/build/Core/BMKeyPath.js +100 -0
- package/build/Core/BMPoint.js +291 -0
- package/build/Core/BMRect.js +556 -0
- package/build/Core/BMSize.js +137 -0
- package/build/iScroll/LICENSE +22 -0
- package/build/iScroll/iscroll-probe.js +2154 -0
- package/build/images/AlignBottom.png +0 -0
- package/build/images/AlignCenterX.png +0 -0
- package/build/images/AlignCenterY.png +0 -0
- package/build/images/AlignLeading.png +0 -0
- package/build/images/AlignTop.png +0 -0
- package/build/images/AlignTrailing.png +0 -0
- package/build/images/AllConstraints.png +0 -0
- package/build/images/BottomConstraint.png +0 -0
- package/build/images/CenterXConstraint.png +0 -0
- package/build/images/CenterYConstraint.png +0 -0
- package/build/images/CoreUI2.png +0 -0
- package/build/images/CoreUI2@2x.png +0 -0
- package/build/images/Desktop.png +0 -0
- package/build/images/DesktopMini.png +0 -0
- package/build/images/EqualHeight.png +0 -0
- package/build/images/EqualHorizontalSpacing.png +0 -0
- package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
- package/build/images/EqualVerticalSpacing.png +0 -0
- package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
- package/build/images/EqualWidth.png +0 -0
- package/build/images/HeightConstraint.png +0 -0
- package/build/images/InactiveConstraints.png +0 -0
- package/build/images/Layout.png +0 -0
- package/build/images/LayoutVariables.png +0 -0
- package/build/images/LeftConstraint.png +0 -0
- package/build/images/OwnConstraints.png +0 -0
- package/build/images/Phone.png +0 -0
- package/build/images/PhoneLandscape.png +0 -0
- package/build/images/PhoneLandscapeMini.png +0 -0
- package/build/images/PhoneMini.png +0 -0
- package/build/images/PhonePortrait.png +0 -0
- package/build/images/PhonePortraitMini.png +0 -0
- package/build/images/Properties.png +0 -0
- package/build/images/RightConstraint.png +0 -0
- package/build/images/SubviewConstraints.png +0 -0
- package/build/images/Tablet.png +0 -0
- package/build/images/TabletLandscape.png +0 -0
- package/build/images/TabletLandscapeMini.png +0 -0
- package/build/images/TabletMini.png +0 -0
- package/build/images/TabletPortrait.png +0 -0
- package/build/images/TabletPortraitMini.png +0 -0
- package/build/images/TopConstraint.png +0 -0
- package/build/images/WidthConstraint.png +0 -0
- package/build/index.js +40 -0
- package/lib/@types/BMCoreUI.min.d.ts +14078 -0
- package/lib/BMCoreUI.min.js +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,3572 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {YES, NO, BMCopyProperties, BMExtend, BMAddSmoothMousewheelInteractionToNode, BMUUIDMake, BMIsTouchDevice} from '../Core/BMCoreUI'
|
|
4
|
+
import {BMInsetMake} from '../Core/BMInset'
|
|
5
|
+
import {BMPointMake} from '../Core/BMPoint'
|
|
6
|
+
import {BMSizeMake} from '../Core/BMSize'
|
|
7
|
+
import {BMRectMake, BMRectMakeWithNodeFrame} from '../Core/BMRect'
|
|
8
|
+
import {BMAnimationContextGetCurrent, BMHook} from '../Core/BMAnimationContext'
|
|
9
|
+
import {BMLayoutSizeClass, BMLayoutOrientation} from './BMLayoutSizeClass'
|
|
10
|
+
import {BMViewport} from './BMViewport'
|
|
11
|
+
import {BMLayoutAttribute, BMLayoutConstraintRelation, BMLayoutConstraint, BMLayoutConstraintPriorityRequired, BMLayoutConstraintKind} from './BMLayoutConstraint_v2.5'
|
|
12
|
+
import * as kiwi from 'kiwi.js'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// When set to YES, this will cause view to use transforms instead of left/right for positioning
|
|
16
|
+
const BM_VIEW_USE_TRANSFORM = NO;
|
|
17
|
+
// When set to YES, this will cause views to flash red when their intrinsic size is measured
|
|
18
|
+
const BM_VIEW_DEBUG_AUTOMATIC_INTRINSIC_SIZE = NO;
|
|
19
|
+
// When set to YES, this will cause messages to appear in the console whenever a layout queue is dequeued or a view hierarchy is enqueued
|
|
20
|
+
// The associated views will also flash yellow upon being enqueued and blue upon being dequeued
|
|
21
|
+
const BM_VIEW_DEBUG_LAYOUT_QUEUE = NO;
|
|
22
|
+
|
|
23
|
+
// @type BMViewConstraintAttribute
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A view constraint attribute represents an object that can be used to simplify the creation of layout constraints.
|
|
27
|
+
* Each view has one property for each available layout attribute. The view constraint attribute has methods that can link
|
|
28
|
+
* it to the attributes of other views, creating a constraint.
|
|
29
|
+
*
|
|
30
|
+
* For example, a constraint is typically created by setting all of its attributes using the factory method `constraintWithView`:
|
|
31
|
+
* ```js
|
|
32
|
+
let constraint = BMLayoutConstraint.constraintWithView(sourceView, {attribute: BMLayoutAttribute.Left, toView: targetView, secondAttribute: BMLayoutAttribute.Left, relatedBy: BMLayoutConstraintRelation.LessThanOrEquals, constant: 8, multiplier: 2});
|
|
33
|
+
```
|
|
34
|
+
*
|
|
35
|
+
* That syntax is quite verbose, so constraint attributes can be used to simplify it:
|
|
36
|
+
* ```js
|
|
37
|
+
let constraint = sourceView.left.lessThanOrEqualTo(targetView.left, {times: 2, plus: 8});
|
|
38
|
+
```
|
|
39
|
+
*
|
|
40
|
+
* Note that the second expression ends up calling the constraint factory method with the same parameters as in the first expression.
|
|
41
|
+
*
|
|
42
|
+
* View constraint attributes cannot be instantiated manually; they are automatically created for each view during initialization.
|
|
43
|
+
*/
|
|
44
|
+
export function BMViewConstraintAttribute() {} // <constructor>
|
|
45
|
+
|
|
46
|
+
BMViewConstraintAttribute.prototype = {
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The layout attribute that this constraint attribute represents.
|
|
50
|
+
*/
|
|
51
|
+
_attribute: undefined, // <BMLayoutAttribute>
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The view affected by this view constraint attribute.
|
|
55
|
+
*/
|
|
56
|
+
_view: undefined, // <BMView>
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Constructs and returns a layout constraint that links this view constraint attribute to constant value.
|
|
60
|
+
* The constraint will be created with an equality sign.
|
|
61
|
+
* @param constant <Number> The constraint's constant.
|
|
62
|
+
* {
|
|
63
|
+
* @param priority <Number, nullable> Defaults to `BMLayoutConstraintPriorityRequired`. The constraint's priority.
|
|
64
|
+
* }
|
|
65
|
+
* @return <BMLayoutConstraint> A layout constraint.
|
|
66
|
+
*/
|
|
67
|
+
// equalTo(constant, {priority}) {
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Constructs and returns a layout constraint that links this view constraint attribute to the given attribute.
|
|
71
|
+
* The constraint will be created with an equality sign.
|
|
72
|
+
* @param attribute <BMViewConstraintAttribute> The attribute to which this attribute should be linked.
|
|
73
|
+
* {
|
|
74
|
+
* @param times <Number, nullable> Defaults to `1`. The constraint's multiplier.
|
|
75
|
+
* @param plus <Number, nullable> Defaults to `0`. The constraint's constant.
|
|
76
|
+
* @param priority <Number, nullable> Defaults to `BMLayoutConstraintPriorityRequired`. The constraint's priority.
|
|
77
|
+
* }
|
|
78
|
+
* @return <BMLayoutConstraint> A layout constraint.
|
|
79
|
+
*/
|
|
80
|
+
equalTo(attribute, {times = 1, plus = 0, priority = BMLayoutConstraintPriorityRequired} = {}) {
|
|
81
|
+
if (typeof attribute == 'number') {
|
|
82
|
+
return BMLayoutConstraint.constraintWithView(this._view, {
|
|
83
|
+
attribute: this._attribute,
|
|
84
|
+
relatedBy: BMLayoutConstraintRelation.Equals,
|
|
85
|
+
constant: attribute,
|
|
86
|
+
priority: priority
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return BMLayoutConstraint.constraintWithView(this._view, {
|
|
90
|
+
attribute: this._attribute,
|
|
91
|
+
relatedBy: BMLayoutConstraintRelation.Equals,
|
|
92
|
+
toView: attribute._view,
|
|
93
|
+
secondAttribute: attribute._attribute,
|
|
94
|
+
constant: plus,
|
|
95
|
+
multiplier: times,
|
|
96
|
+
priority: priority
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Constructs and returns a layout constraint that links this view constraint attribute to constant value.
|
|
102
|
+
* The constraint will be created with a less than or equals sign.
|
|
103
|
+
* @param constant <Number> The constraint's constant.
|
|
104
|
+
* {
|
|
105
|
+
* @param priority <Number, nullable> Defaults to `BMLayoutConstraintPriorityRequired`. The constraint's priority.
|
|
106
|
+
* }
|
|
107
|
+
* @return <BMLayoutConstraint> A layout constraint.
|
|
108
|
+
*/
|
|
109
|
+
// lessThanOrEqualTo(constant, {priority}) {
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Constructs and returns a layout constraint that links this view constraint attribute to the given attribute.
|
|
113
|
+
* The constraint will be created with a less than or equals sign.
|
|
114
|
+
* @param attribute <BMViewConstraintAttribute> The attribute to which this attribute should be linked.
|
|
115
|
+
* {
|
|
116
|
+
* @param times <Number, nullable> Defaults to `1`. The constraint's multiplier.
|
|
117
|
+
* @param plus <Number, nullable> Defaults to `0`. The constraint's constant.
|
|
118
|
+
* @param priority <Number, nullable> Defaults to `BMLayoutConstraintPriorityRequired`. The constraint's priority.
|
|
119
|
+
* }
|
|
120
|
+
* @return <BMLayoutConstraint> A layout constraint.
|
|
121
|
+
*/
|
|
122
|
+
lessThanOrEqualTo(attribute, {times = 1, plus = 0, priority = BMLayoutConstraintPriorityRequired} = {}) {
|
|
123
|
+
if (typeof attribute == 'number') {
|
|
124
|
+
return BMLayoutConstraint.constraintWithView(this._view, {
|
|
125
|
+
attribute: this._attribute,
|
|
126
|
+
relatedBy: BMLayoutConstraintRelation.LessThanOrEquals,
|
|
127
|
+
constant: attribute,
|
|
128
|
+
priority: priority
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return BMLayoutConstraint.constraintWithView(this._view, {
|
|
132
|
+
attribute: this._attribute,
|
|
133
|
+
relatedBy: BMLayoutConstraintRelation.LessThanOrEquals,
|
|
134
|
+
toView: attribute._view,
|
|
135
|
+
secondAttribute: attribute._attribute,
|
|
136
|
+
constant: plus,
|
|
137
|
+
multiplier: times,
|
|
138
|
+
priority: priority
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Constructs and returns a layout constraint that links this view constraint attribute to constant value.
|
|
144
|
+
* The constraint will be created with a greather than or equals sign.
|
|
145
|
+
* @param constant <Number> The constraint's constant.
|
|
146
|
+
* {
|
|
147
|
+
* @param priority <Number, nullable> Defaults to `BMLayoutConstraintPriorityRequired`. The constraint's priority.
|
|
148
|
+
* }
|
|
149
|
+
* @return <BMLayoutConstraint> A layout constraint.
|
|
150
|
+
*/
|
|
151
|
+
// greaterThanOrEqualTo(constant, {priority}) {
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Constructs and returns a layout constraint that links this view constraint attribute to the given attribute.
|
|
155
|
+
* The constraint will be created with a greater than or equals sign.
|
|
156
|
+
* @param attribute <BMViewConstraintAttribute> The attribute to which this attribute should be linked.
|
|
157
|
+
* {
|
|
158
|
+
* @param times <Number, nullable> Defaults to `1`. The constraint's multiplier.
|
|
159
|
+
* @param plus <Number, nullable> Defaults to `0`. The constraint's constant.
|
|
160
|
+
* @param priority <Number, nullable> Defaults to `BMLayoutConstraintPriorityRequired`. The constraint's priority.
|
|
161
|
+
* }
|
|
162
|
+
* @return <BMLayoutConstraint> A layout constraint.
|
|
163
|
+
*/
|
|
164
|
+
greaterThanOrEqualTo(attribute, {times = 1, plus = 0, priority = BMLayoutConstraintPriorityRequired} = {}) {
|
|
165
|
+
if (typeof attribute == 'number') {
|
|
166
|
+
return BMLayoutConstraint.constraintWithView(this._view, {
|
|
167
|
+
attribute: this._attribute,
|
|
168
|
+
relatedBy: BMLayoutConstraintRelation.GreaterThanOrEquals,
|
|
169
|
+
constant: attribute,
|
|
170
|
+
priority: priority
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return BMLayoutConstraint.constraintWithView(this._view, {
|
|
174
|
+
attribute: this._attribute,
|
|
175
|
+
relatedBy: BMLayoutConstraintRelation.GreaterThanOrEquals,
|
|
176
|
+
toView: attribute._view,
|
|
177
|
+
secondAttribute: attribute._attribute,
|
|
178
|
+
constant: plus,
|
|
179
|
+
multiplier: times,
|
|
180
|
+
priority: priority
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// @endtype
|
|
187
|
+
|
|
188
|
+
// @type BMViewLayoutQueue
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A layout queue is an object used to coordinate layout passes across different view hierarchies to minimize DOM thrashing due to automatic
|
|
192
|
+
* intrinsic size calculation. CoreUI, by default, creates a global layout queue that is shared by all new view instances.
|
|
193
|
+
*
|
|
194
|
+
* Views that are bound to the same layout queue will run their scheduled layout passes in sync. As such, whenever a view's layout process
|
|
195
|
+
* invalidates the DOM, it will wait for all other views in the same queue to reach the same point in the process before starting to read
|
|
196
|
+
* the updated DOM.
|
|
197
|
+
*
|
|
198
|
+
* To create a layout queue, use the static `layoutQueue()` factory method.
|
|
199
|
+
*/
|
|
200
|
+
export function BMViewLayoutQueue() {} // <constructor>
|
|
201
|
+
|
|
202
|
+
BMViewLayoutQueue.prototype = {
|
|
203
|
+
/**
|
|
204
|
+
* A set that contains views bound to this layout queue that have pending layout passes.
|
|
205
|
+
*/
|
|
206
|
+
_views: undefined, // <Set<BMView>>
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Adds a view to this queue.
|
|
210
|
+
* @param view <BMView> The view to add to the queue.
|
|
211
|
+
*/
|
|
212
|
+
_enqueueView(view) {
|
|
213
|
+
if (BM_VIEW_DEBUG_LAYOUT_QUEUE && this._views.has(view)) {
|
|
214
|
+
console.error('[BMViewLayoutQueue] Enqueueing view ' + view.debuggingName + ' into queue ' + this._identifier + '.');
|
|
215
|
+
let flash = document.createElement('div');
|
|
216
|
+
flash.style.cssText = 'position: absolute; z-index: 999999999; top: 0px; left: 0px; width: 100%; height: 100%; background-color: yellow; pointer-events: none;';
|
|
217
|
+
view.node.appendChild(flash);
|
|
218
|
+
requestAnimationFrame(() => flash.remove());
|
|
219
|
+
}
|
|
220
|
+
this._views.add(view);
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Removes a view from this queue.
|
|
225
|
+
* @param view <BMView> The view to remove.
|
|
226
|
+
*/
|
|
227
|
+
_removeView(view) {
|
|
228
|
+
this._views.delete(view);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Runs a synchronized layout pass on the views in this queue and drains the queue.
|
|
233
|
+
*/
|
|
234
|
+
dequeue() {
|
|
235
|
+
if (BM_VIEW_DEBUG_LAYOUT_QUEUE && this._views.size) {
|
|
236
|
+
console.error('[BMViewLayoutQueue] Dequeueing queue ' + this._identifier + ' with ' + this._views.size + ' view hierarchies.');
|
|
237
|
+
for (let view of this._views) {
|
|
238
|
+
let flash = document.createElement('div');
|
|
239
|
+
flash.style.cssText = 'position: absolute; z-index: 999999999; top: 0px; left: 0px; width: 100%; height: 100%; background-color: blue; pointer-events: none;';
|
|
240
|
+
view.node.appendChild(flash);
|
|
241
|
+
requestAnimationFrame(() => flash.remove());
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
_BMViewDequeueLayoutQueue(this);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Constructs and returns a new layout queue.
|
|
250
|
+
* @return <BMViewLayoutQueue> The layout queue.
|
|
251
|
+
*/
|
|
252
|
+
BMViewLayoutQueue.layoutQueue = function () {
|
|
253
|
+
let queue = new BMViewLayoutQueue();
|
|
254
|
+
|
|
255
|
+
queue._views = new Set;
|
|
256
|
+
queue._identifier = BMUUIDMake();
|
|
257
|
+
|
|
258
|
+
return queue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// @endtype
|
|
262
|
+
|
|
263
|
+
// @type BMView
|
|
264
|
+
|
|
265
|
+
const BMViewDebug = NO;
|
|
266
|
+
|
|
267
|
+
// An array holding views that have pending layout passes so that they can be processed together in a single event,
|
|
268
|
+
// batching together the various DOM reads and writes that the views perform
|
|
269
|
+
const _BMViewLayoutQueue = BMViewLayoutQueue.layoutQueue();
|
|
270
|
+
|
|
271
|
+
// Invoked by CoreUI to dequeue and layout views which have pending layout passes
|
|
272
|
+
// @param queue <BMViewLayoutQueue, nullable> Defaults to the global queue. The queue to dequeue.
|
|
273
|
+
const _BMViewDequeueLayoutQueue = function (layoutQueue) {
|
|
274
|
+
// Create a separate layout queue which will process future layout passes, in case any view invalidates its layout
|
|
275
|
+
// during the layout pass (e.g. through overriden methods invoked on subclasses), which would cause the process to desynchronize
|
|
276
|
+
let queue;
|
|
277
|
+
if (layoutQueue) {
|
|
278
|
+
queue = [...layoutQueue._views];
|
|
279
|
+
layoutQueue._views = new Set;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
queue = [..._BMViewLayoutQueue._views];
|
|
283
|
+
_BMViewLayoutQueue._views = new Set;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Transform the queue from an array of views into an array of associated layout iterators
|
|
287
|
+
queue = queue.map(view => view._layoutSubviewsGenerator());
|
|
288
|
+
|
|
289
|
+
// Run the iterations together, one step at a time for each view
|
|
290
|
+
let hasIterations = YES;
|
|
291
|
+
while (hasIterations) {
|
|
292
|
+
hasIterations = !queue.reduce((accumulator, iterator) => {
|
|
293
|
+
return iterator.next().done && accumulator;
|
|
294
|
+
}, YES);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* A view is a wrapper around a DOM node enabling various CoreUI-related functionality.
|
|
300
|
+
* Views typically do not duplicate existing DOM capabilities, but instead are used to
|
|
301
|
+
* attach additional information and functionality to those nodes.
|
|
302
|
+
*
|
|
303
|
+
* Views are usually not constructed using any constructor function. Instead, their lifecycle is managed by CoreUI.
|
|
304
|
+
* You obtain views by invoking the static <code>viewForNode(_)</code> method on the <code>BMView</code> type.
|
|
305
|
+
* It is also not necessary to invoke any destruction method when the DOM nodes managed by views are removed.
|
|
306
|
+
*
|
|
307
|
+
* To extend from BMView and create a view subtype, extend from the BMView prototype, then, when requesting views use
|
|
308
|
+
* <code>BMView.viewForNode.call(MyCustomViewType, node)</code>. Note that if the node already had
|
|
309
|
+
* a view associated with it, the method above will return the original view reference of whatever type
|
|
310
|
+
* it was created with. Subclasses may also provide their own construction methods built atop of <code>viewForNode</code>.
|
|
311
|
+
* For view subclasses that create and manage their own DOM content, it is sufficient to invoke <code>BMView</code>'s
|
|
312
|
+
* designated initializer after construction.
|
|
313
|
+
*/
|
|
314
|
+
export function BMView() {} // <constructor>
|
|
315
|
+
|
|
316
|
+
(function () {
|
|
317
|
+
// A private map maintaining the link between DOM nodes and their associated view objects.
|
|
318
|
+
var _BMViewMap = new WeakMap; // <WeakMap<DOMNode, BMView>>
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Returns the view object associated with the given DOM node.
|
|
322
|
+
* @param node <DOMNode> The DOM node for which to retrieve the view.
|
|
323
|
+
* @return <BMView> A view.
|
|
324
|
+
*/
|
|
325
|
+
BMView.viewForNode = function (node) {
|
|
326
|
+
let view = _BMViewMap.get(node);
|
|
327
|
+
|
|
328
|
+
if (!view) {
|
|
329
|
+
view = Object.create(this.prototype);
|
|
330
|
+
|
|
331
|
+
return view.initWithDOMNode(node);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return view;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Constructs a div node and returns the view object associated with it.
|
|
339
|
+
* The newly created node will not be attached to any parent - it must be manually
|
|
340
|
+
* added to the document's node hierarchy in order to be used.
|
|
341
|
+
* @return <BMView> A view.
|
|
342
|
+
*/
|
|
343
|
+
BMView.view = function () {
|
|
344
|
+
const node = document.createElement('div');
|
|
345
|
+
const view = Object.create(this.prototype).initWithDOMNode(node);
|
|
346
|
+
//_BMViewMap.set(node, view);
|
|
347
|
+
return view;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// A set that contains the views that are root views.
|
|
351
|
+
// These are the view that will handle updates to layout variables.
|
|
352
|
+
const rootViews = new Set;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Used internally by CoreUI to mark a given view as a root view and allow it to be
|
|
356
|
+
* notified of updates to layout variables.
|
|
357
|
+
*
|
|
358
|
+
* For the purposes of receiving layout variable updates, a view is considered to be a root view
|
|
359
|
+
* if it has no superview and has at least one subview.
|
|
360
|
+
* @param view <BMView> The view to become a root view.
|
|
361
|
+
*/
|
|
362
|
+
BMView._markAsRootView = function (view) {
|
|
363
|
+
rootViews.add(view);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Used internally by CoreUI to mark a given view that was previously considered
|
|
368
|
+
* to be a root view as a non-root view. The view will no longer be notified of
|
|
369
|
+
* updates to layout variables.
|
|
370
|
+
*
|
|
371
|
+
* A view is considered to be a non-root view if it has a superview or if it contains no subviews.
|
|
372
|
+
* @param view <BMView> The view to become a root view.
|
|
373
|
+
*/
|
|
374
|
+
BMView._markAsNonRootView = function (view) {
|
|
375
|
+
rootViews.delete(view);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// An object containing the registered layout variables and their variations
|
|
379
|
+
const layoutVariables = {};
|
|
380
|
+
const layoutVariableVariations = {};
|
|
381
|
+
|
|
382
|
+
Object.defineProperty(BMView, 'layoutVariables', {get() {
|
|
383
|
+
const variables = {};
|
|
384
|
+
return BMCopyProperties(variables, layoutVariables);
|
|
385
|
+
}});
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Included for compatibility with layout editors. This method does nothing.
|
|
389
|
+
*/
|
|
390
|
+
BMView.prepareLayoutVariables = function () {
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* This method returns `YES` to indicate that global layout variables are available for use and editing.
|
|
396
|
+
* @return <Boolean> `YES`.
|
|
397
|
+
*/
|
|
398
|
+
BMView.canUseLayoutVariables = function () {
|
|
399
|
+
return YES;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Returns an array describing the variations of the given layout variable.
|
|
404
|
+
* @param named <String> The name of the layout variable.
|
|
405
|
+
* @return <[BMLayoutVariableVariation]> An array of variations for the given layout variable.
|
|
406
|
+
* If no variations have been defined, the array will be empty.
|
|
407
|
+
*/
|
|
408
|
+
BMView.variationsForLayoutVariableNamed = function (named) {
|
|
409
|
+
const variations = [];
|
|
410
|
+
let variation;
|
|
411
|
+
|
|
412
|
+
for (let key in layoutVariableVariations) {
|
|
413
|
+
if (named in layoutVariableVariations[key]) {
|
|
414
|
+
variation = layoutVariableVariations[key][named];
|
|
415
|
+
variations.push({name: named, sizeClass: layoutVariableVariations[key].sizeClass, value: variation});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return variations;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Registers a layout variable that can be used by layout constraints in this environment
|
|
424
|
+
* as values for the constant property. CoreUI will resolve the actual numeric value of the constant
|
|
425
|
+
* based on the size class variations introduced for that layout variable.
|
|
426
|
+
* If a layout variable with the given name already exists, its default value will be replaced
|
|
427
|
+
* by the given value.
|
|
428
|
+
* @param named <String> The name to use for this layout variable.
|
|
429
|
+
* {
|
|
430
|
+
* @param withValue <Number> The default value to use for this layout variable, when there are no
|
|
431
|
+
* active size classes variations.
|
|
432
|
+
* }
|
|
433
|
+
*/
|
|
434
|
+
BMView.registerLayoutVariableNamed = function (named, {withValue: value}) {
|
|
435
|
+
layoutVariables[named] = value;
|
|
436
|
+
|
|
437
|
+
// Notify the root views of this update
|
|
438
|
+
for (const view of rootViews) {
|
|
439
|
+
view._layoutVariablesDidUpdate();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Renames an existing layout variable. If another layout variable already has the new name
|
|
445
|
+
* this method will raise an error.
|
|
446
|
+
*
|
|
447
|
+
* Note that this will not update existing references to the layout variable. The variable being renamed
|
|
448
|
+
* should not be referenced by any layout constraint otherwise this might cause the layout process
|
|
449
|
+
* to fail in subsequent layout passes if the references are not updated.
|
|
450
|
+
*
|
|
451
|
+
* If a variable with the current name does not exist, this method does nothing.
|
|
452
|
+
* @param named <String> The layout variable's current name.
|
|
453
|
+
* {
|
|
454
|
+
* @param toName <String> The new name to use for the layout variable.
|
|
455
|
+
* }
|
|
456
|
+
*/
|
|
457
|
+
BMView.renameLayoutVariableNamed = function (named, {toName: newName}) {
|
|
458
|
+
if (named in layoutVariables) {
|
|
459
|
+
if (newName in layoutVariables) {
|
|
460
|
+
throw new Error('A layout variable with the given name already exists.');
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Update the variable
|
|
464
|
+
layoutVariables[newName] = layoutVariables[named];
|
|
465
|
+
delete layoutVariables[named];
|
|
466
|
+
|
|
467
|
+
// And also all of its variations
|
|
468
|
+
let variation;
|
|
469
|
+
for (let key in layoutVariableVariations) {
|
|
470
|
+
if (named in layoutVariableVariations[key]) {
|
|
471
|
+
layoutVariableVariations[key][newName] = layoutVariableVariations[key][named];
|
|
472
|
+
delete layoutVariableVariations[key][named];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Notify the root views of this update
|
|
477
|
+
for (const view of rootViews) {
|
|
478
|
+
view._layoutVariablesDidUpdate();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Unregisters the given layout variable if it had been previously registered.
|
|
485
|
+
* If a layout variable with the given name has not been previously registered,
|
|
486
|
+
* this method does nothing.
|
|
487
|
+
*
|
|
488
|
+
* This layout variable should not be referenced by any constraints when unregistered,
|
|
489
|
+
* otherwise this might cause the layout process to fail in subsequent layout passes.
|
|
490
|
+
* @param named <String> The name of the layout variable to remove.
|
|
491
|
+
*/
|
|
492
|
+
BMView.unregisterLayoutVariableNamed = function (named) {
|
|
493
|
+
delete layoutVariables[named];
|
|
494
|
+
|
|
495
|
+
// Notify the root views of this update
|
|
496
|
+
for (const view of rootViews) {
|
|
497
|
+
view._layoutVariablesDidUpdate();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Sets a variation for the given layout variable when the given size class is active.
|
|
503
|
+
* If a variation for the given layout variable already exists for the given size class,
|
|
504
|
+
* its value is updated to the specified value.
|
|
505
|
+
* @param value <Number> The value for the given layout variable when the given size class is active.
|
|
506
|
+
* {
|
|
507
|
+
* @param named <String> The name of the layout variable.
|
|
508
|
+
* @param inSizeClass <BMLayoutSizeClass> The size class for which this variation will be active.
|
|
509
|
+
* }
|
|
510
|
+
*/
|
|
511
|
+
BMView.setLayoutVariableValue = function (value, {named: name, inSizeClass: sizeClass}) {
|
|
512
|
+
// Create the variations entry for the size class if it doesn't already exist
|
|
513
|
+
layoutVariableVariations[sizeClass._hashString] = layoutVariableVariations[sizeClass._hashString] || {sizeClass: sizeClass};
|
|
514
|
+
|
|
515
|
+
// Then register the variation
|
|
516
|
+
layoutVariableVariations[sizeClass._hashString][name] = value;
|
|
517
|
+
|
|
518
|
+
// Notify the root views of this update
|
|
519
|
+
for (const view of rootViews) {
|
|
520
|
+
view._layoutVariablesDidUpdate();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Removes a variation for the given layout variable for the given size class.
|
|
526
|
+
* If such a variation doesn't exist, this method does nothing.
|
|
527
|
+
* @param name <String> The name of the layout variable.
|
|
528
|
+
* {
|
|
529
|
+
* @param inSizeClass <BMLayoutSizeClass> The size class from which to remove this variation.
|
|
530
|
+
* }
|
|
531
|
+
*/
|
|
532
|
+
BMView.removeVariationForLayoutVariableNamed = function (name, {inSizeClass: sizeClass}) {
|
|
533
|
+
if (layoutVariableVariations[sizeClass._hashString]) {
|
|
534
|
+
delete layoutVariableVariations[sizeClass._hashString][name];
|
|
535
|
+
|
|
536
|
+
// If there are no more variations for the given size class, remove the variation entirely
|
|
537
|
+
if (Object.keys(layoutVariableVariations[sizeClass._hashString]).length == 1) {
|
|
538
|
+
delete layoutVariableVariations[sizeClass._hashString];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Notify the root views of this update
|
|
542
|
+
for (const view of rootViews) {
|
|
543
|
+
view._layoutVariablesDidUpdate();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Removes all variations for the given layout variable for all given size class.
|
|
550
|
+
* If no such variations exist, this method does nothing.
|
|
551
|
+
* @param name <String> The name of the layout variable.
|
|
552
|
+
*/
|
|
553
|
+
BMView.removeVariationsForLayoutVariableNamed = function (name) {
|
|
554
|
+
for (let sizeClass in layoutVariableVariations) {
|
|
555
|
+
delete layoutVariableVariations[sizeClass][name];
|
|
556
|
+
|
|
557
|
+
// If there are no more variations for the given size class, remove the variation entirely
|
|
558
|
+
if (Object.keys(layoutVariableVariations[sizeClass]).length == 1) {
|
|
559
|
+
delete layoutVariableVariations[sizeClass];
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Notify the root views of this update
|
|
564
|
+
for (const view of rootViews) {
|
|
565
|
+
view._layoutVariablesDidUpdate();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Included for compatibility with layout editors. This method does nothing.
|
|
571
|
+
*/
|
|
572
|
+
BMView.persistLayoutVariables = function () {
|
|
573
|
+
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Returns and caches an object that describes the current values of the registered layout variables for the given viewport.
|
|
578
|
+
* @return <Dictionary<Number>> The current layout variable values.
|
|
579
|
+
*/
|
|
580
|
+
BMView.prototype._layoutVariableValuesForViewport = function (viewport) {
|
|
581
|
+
let values = {};
|
|
582
|
+
|
|
583
|
+
for (let key of Object.keys(layoutVariables)) {
|
|
584
|
+
values[key] = layoutVariables[key];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Check which variations should be active and in which order
|
|
588
|
+
for (let key in layoutVariableVariations) {
|
|
589
|
+
layoutVariableVariations[key]._matchPriority = viewport.matchPriorityForSizeClass(layoutVariableVariations[key].sizeClass);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
Object.keys(layoutVariableVariations).map(key => layoutVariableVariations[key]).sort((a, b) => b._matchPriority - a._matchPriority).forEach(variation => {
|
|
593
|
+
if (!variation._matchPriority) return;
|
|
594
|
+
|
|
595
|
+
// Registered properties
|
|
596
|
+
for (let key in layoutVariables) {
|
|
597
|
+
if (key in variation) {
|
|
598
|
+
values[key] = variation[key];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
this._layoutVariables = values;
|
|
604
|
+
|
|
605
|
+
return values;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Used when releasing an entire view hierarchy.
|
|
610
|
+
* Releases this view without affecting constraints or the DOM.
|
|
611
|
+
*
|
|
612
|
+
* The final operations will be performed by the view that initiated this release
|
|
613
|
+
* operation.
|
|
614
|
+
*/
|
|
615
|
+
BMView.prototype._releaseRecursive = function () {
|
|
616
|
+
if (this.__released) {
|
|
617
|
+
// This needs to be handled because of the way Thingworx DOM nodes are removed and recreated in the composer
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
this.__released = YES;
|
|
621
|
+
|
|
622
|
+
for (const view of this._subviews) {
|
|
623
|
+
view._releaseRecursive();
|
|
624
|
+
}
|
|
625
|
+
_BMViewMap.delete(this._node);
|
|
626
|
+
rootViews.delete(this);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Invoked when this view is no longer needed.
|
|
631
|
+
* Removes the view from its superview and removes all event listeners
|
|
632
|
+
* created by this view but otherwise leaves its DOM node intact.
|
|
633
|
+
*
|
|
634
|
+
* This view should not be reused after invoking this method. Instead, if needed,
|
|
635
|
+
* a new view should be obtained for that node and used.
|
|
636
|
+
*/
|
|
637
|
+
BMView.prototype.release = function () {
|
|
638
|
+
if (this.__released) {
|
|
639
|
+
// This needs to be handled because of the way Thingworx DOM nodes are removed and recreated in the composer
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
this.__released = YES;
|
|
643
|
+
|
|
644
|
+
this.layoutQueue._removeView(this);
|
|
645
|
+
|
|
646
|
+
for (const view of this._subviews.slice()) {
|
|
647
|
+
view._releaseRecursive();
|
|
648
|
+
}
|
|
649
|
+
_BMViewMap.delete(this._node);
|
|
650
|
+
if (this._superview) {
|
|
651
|
+
this.removeFromSuperview();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
rootViews.delete(this);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Designated initializer, invoked immediately after any view is created.
|
|
659
|
+
* Subclasses must invoke this base initializer at some point during their initialization.
|
|
660
|
+
* @param node <DOMNode> The DOM node that will be managed by this view.
|
|
661
|
+
* @return <BMView> This view.
|
|
662
|
+
*/
|
|
663
|
+
BMView.prototype.initWithDOMNode = function (node) {
|
|
664
|
+
this._node = node;
|
|
665
|
+
this._constraints = [];
|
|
666
|
+
this._subviews = [];
|
|
667
|
+
|
|
668
|
+
var currentView;
|
|
669
|
+
if (currentView = _BMViewMap.get(node)) {
|
|
670
|
+
if (currentView != this) {
|
|
671
|
+
throw new Error('There is already a view associated with the given node.');
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
_BMViewMap.set(node, this);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// As they are ultimately used to determine the view's frame,
|
|
679
|
+
// left, width, top and height variables are the only ones actually used in
|
|
680
|
+
// layout calculations
|
|
681
|
+
// Right, Center and Bottom constraints are expressed through equalities
|
|
682
|
+
// between these four attributes
|
|
683
|
+
this._variables = {
|
|
684
|
+
[BMLayoutAttribute.Left]: new kiwi.Variable(this.node.id + '.' + BMLayoutAttribute.Left),
|
|
685
|
+
[BMLayoutAttribute.Width]: new kiwi.Variable(this.node.id + '.' + BMLayoutAttribute.Width),
|
|
686
|
+
|
|
687
|
+
[BMLayoutAttribute.Top]: new kiwi.Variable(this.node.id + '.' + BMLayoutAttribute.Top),
|
|
688
|
+
[BMLayoutAttribute.Height]: new kiwi.Variable(this.node.id + '.' + BMLayoutAttribute.Height)
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// Create a view constraint attribute for each available layout attribute
|
|
692
|
+
for (let key in BMLayoutAttribute) {
|
|
693
|
+
let propertyName = key.substring(0, 1).toLowerCase() + key.substring(1, key.length);
|
|
694
|
+
let attribute = Object.create(BMViewConstraintAttribute.prototype);
|
|
695
|
+
|
|
696
|
+
attribute._view = this;
|
|
697
|
+
attribute._attribute = BMLayoutAttribute[key];
|
|
698
|
+
|
|
699
|
+
this[propertyName] = attribute;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Initialize collections
|
|
703
|
+
this._sizeClasses = new Set;
|
|
704
|
+
this._variations = {};
|
|
705
|
+
this._configuration = {opacity: this._opacity, isVisible: this._isVisible, contentInsets: this.__activeInsets};
|
|
706
|
+
this._variableProperties = {};
|
|
707
|
+
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
})();
|
|
712
|
+
|
|
713
|
+
BMView.prototype = BMExtend(BMView.prototype, {
|
|
714
|
+
|
|
715
|
+
// #region Base Properties
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* The DOM node managed by this view.
|
|
719
|
+
*/
|
|
720
|
+
_node: undefined, // <DOMNode>
|
|
721
|
+
get node() {
|
|
722
|
+
return this._node;
|
|
723
|
+
},
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* The DOM node to which subviews will be added.
|
|
727
|
+
* This should be a descendant of the node returned by the <code>node</code> property.
|
|
728
|
+
*
|
|
729
|
+
* Subclasses which manage a bigger node hierarchy should override this getter
|
|
730
|
+
* and return the appropriate node within their hierarchy to let CoreUI know
|
|
731
|
+
* where to insert subviews.
|
|
732
|
+
*
|
|
733
|
+
* The default implementation returns the same value as the <code>node</code> property.
|
|
734
|
+
*/
|
|
735
|
+
get contentNode() { // <DOMNode>
|
|
736
|
+
return this._node;
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* An optional name used to identify this view when printing out debug messages.
|
|
741
|
+
* This name is also used by the layout editor when displaying this view.
|
|
742
|
+
*/
|
|
743
|
+
debuggingName: '', // <String, nullable>
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* The attribute corresponding to a view's leading edge.
|
|
748
|
+
* This is the same as the left edge in a left-to-right layout and
|
|
749
|
+
* the same as the right edge in a right-to-left layout.
|
|
750
|
+
*/
|
|
751
|
+
leading: undefined, // <BMViewConstraintAttribute>
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* The attribute corresponding to a view's trailing edge.
|
|
756
|
+
* This is the same as the right edge in a left-to-right layout and
|
|
757
|
+
* the same as the left edge in a right-to-left layout.
|
|
758
|
+
*/
|
|
759
|
+
trailing: undefined, // <BMViewConstraintAttribute>
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* The attribute corresponding to a view's left edge.
|
|
764
|
+
*/
|
|
765
|
+
left: undefined, // <BMViewConstraintAttribute>
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* The attribute corresponding to a view's right edge.
|
|
770
|
+
*/
|
|
771
|
+
right: undefined, // <BMViewConstraintAttribute>
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* The attribute corresponding to a view's top edge.
|
|
776
|
+
*/
|
|
777
|
+
top: undefined, // <BMViewConstraintAttribute>
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* The attribute corresponding to a view's bottom edge.
|
|
782
|
+
*/
|
|
783
|
+
bottom: undefined, // <BMViewConstraintAttribute>
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* The attribute corresponding to a view's horizontal center.
|
|
788
|
+
*/
|
|
789
|
+
centerX: undefined, // <BMViewConstraintAttribute>
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* The attribute corresponding to a view's vertical center.
|
|
794
|
+
*/
|
|
795
|
+
centerY: undefined, // <BMViewConstraintAttribute>
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* The attribute corresponding to a view's width.
|
|
800
|
+
*/
|
|
801
|
+
width: undefined, // <BMViewConstraintAttribute>
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* The attribute corresponding to a view's height.
|
|
806
|
+
*/
|
|
807
|
+
height: undefined, // <BMViewConstraintAttribute>
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* The layout editor currently editing this view's layout.
|
|
811
|
+
*/
|
|
812
|
+
_layoutEditor: undefined, // <BMLayoutEditor, nullable>
|
|
813
|
+
get layoutEditor() {
|
|
814
|
+
return this._layoutEditor;
|
|
815
|
+
},
|
|
816
|
+
set layoutEditor(editor) {
|
|
817
|
+
this._layoutEditor = editor;
|
|
818
|
+
},
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* The layout queue on which this view processes its layout passes.
|
|
822
|
+
* If this property is modified while this view had already registered a layout pass with a different queue,
|
|
823
|
+
* that layout pass will still run on that queue the next time it is drained.
|
|
824
|
+
*/
|
|
825
|
+
_layoutQueue: _BMViewLayoutQueue, // <BMViewLayoutQueue, nullResettable>
|
|
826
|
+
get layoutQueue() {
|
|
827
|
+
return this._layoutQueue;
|
|
828
|
+
},
|
|
829
|
+
set layoutQueue(queue) {
|
|
830
|
+
this._layoutQueue._removeView(this);
|
|
831
|
+
this._layoutQueue = queue || _BMViewLayoutQueue;
|
|
832
|
+
},
|
|
833
|
+
|
|
834
|
+
// #endregion
|
|
835
|
+
|
|
836
|
+
// #region Frame and Bounds
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* A promise that it is initialized upon this view starting a layout animation and resolved when
|
|
840
|
+
* the animation is finished.
|
|
841
|
+
*/
|
|
842
|
+
_layoutAnimator: undefined, // <Promise<Void>, nullable>
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Animatable.
|
|
846
|
+
* A rectangle describing this view's size and position relative to its superview.
|
|
847
|
+
* If the view hierarchy that this view belongs to does not match the DOM node hierarchy, the coordinates
|
|
848
|
+
* of this view's frame are not guaranteed to match its position within the document.
|
|
849
|
+
*
|
|
850
|
+
* For views whose layout is managed by CoreUI, this will return the view's frame as it is obtained by
|
|
851
|
+
* resolving the layout constraints. This value should not be set manually in those cases.
|
|
852
|
+
*
|
|
853
|
+
* Setting this value will cause the node managed by this view to have its position set to absolute and
|
|
854
|
+
* its size and position styles set to match the new frame's values.
|
|
855
|
+
*
|
|
856
|
+
* After setting this property, the position and size of the node managed by this view will be managed
|
|
857
|
+
* by CoreUI and should not be modified by outside means (e.g. through CSS).
|
|
858
|
+
*/
|
|
859
|
+
_frame: undefined, // <BMRect>
|
|
860
|
+
get frame() {
|
|
861
|
+
return this._frame || BMRectMakeWithNodeFrame(this._node);
|
|
862
|
+
},
|
|
863
|
+
|
|
864
|
+
set frame(frame) {
|
|
865
|
+
|
|
866
|
+
// When the frame is assigned for the first time, make the node have an absolute positioning
|
|
867
|
+
if (!this._frame) {
|
|
868
|
+
this._node.style.position = 'absolute';
|
|
869
|
+
this._node.style.contain = 'layout';
|
|
870
|
+
this._node.style.right = 'auto';
|
|
871
|
+
this._node.style.bottom = 'auto';
|
|
872
|
+
this._node.style.left = '0px';
|
|
873
|
+
this._node.style.top = '0px';
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// If this new frame will cause the bounds to change, allow the view to react to the pending change
|
|
877
|
+
let boundsWillChange = NO;
|
|
878
|
+
let newBounds;
|
|
879
|
+
if (!this._frame || !this._frame.size.isEqualToSize(frame.size)) {
|
|
880
|
+
// Compute the new bounds
|
|
881
|
+
newBounds = frame.copy();
|
|
882
|
+
newBounds.origin = BMPointMake();
|
|
883
|
+
|
|
884
|
+
this.boundsWillChangeToBounds(newBounds);
|
|
885
|
+
boundsWillChange = YES;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (this.isRootView) {
|
|
889
|
+
if (!this._frame) {
|
|
890
|
+
// Root view has static positioning and size regardless of the frame
|
|
891
|
+
if (BM_VIEW_USE_TRANSFORM) {
|
|
892
|
+
BMHook(this._node, {translateX: '0px', translateY: '0px'});
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
this._node.style.top = '0px';
|
|
896
|
+
this._node.style.left = '0px';
|
|
897
|
+
}
|
|
898
|
+
this._node.style.width = '100%';
|
|
899
|
+
this._node.style.height = '100%';
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
// The width and height need to be re-set each time because they may be modified
|
|
903
|
+
// by automatic intrinsic size calculations
|
|
904
|
+
this._node.style.width = '100%';
|
|
905
|
+
this._node.style.height = '100%';
|
|
906
|
+
}
|
|
907
|
+
this._frame = frame.copy();
|
|
908
|
+
|
|
909
|
+
if (this._layoutEditor) {
|
|
910
|
+
this._layoutEditor.viewDidLayoutSubviews(this);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// Make this change animated if there is an animation currently running
|
|
914
|
+
else if (BMAnimationContextGetCurrent()) {
|
|
915
|
+
if (!this._frame) {
|
|
916
|
+
this._frame = BMRectMakeWithNodeFrame(this.node);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Assign back the coordinates of the previous frame
|
|
920
|
+
if (BM_VIEW_USE_TRANSFORM) {
|
|
921
|
+
BMHook(this._node, {translateX: this._frame.origin.x + 'px', translateY: this._frame.origin.y + 'px'});
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
BMHook(this._node, {left: this._frame.origin.x + 'px', top: this._frame.origin.y + 'px'});
|
|
925
|
+
}
|
|
926
|
+
this._node.style.width = this._frame.size.width + 'px';
|
|
927
|
+
this._node.style.height = this._frame.size.height + 'px';
|
|
928
|
+
|
|
929
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._node});
|
|
930
|
+
//controller.registerAnimatableProperty('_frame', {targetValue: frame.copy(), startingValue: this._frame.copy()});
|
|
931
|
+
if (BM_VIEW_USE_TRANSFORM) {
|
|
932
|
+
controller.registerBuiltInProperty('translateX', {withValue: frame.origin.x + 'px'});
|
|
933
|
+
controller.registerBuiltInProperty('translateY', {withValue: frame.origin.y + 'px'});
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
controller.registerBuiltInProperty('left', {withValue: frame.origin.x + 'px'});
|
|
937
|
+
controller.registerBuiltInProperty('top', {withValue: frame.origin.y + 'px'});
|
|
938
|
+
}
|
|
939
|
+
controller.registerBuiltInProperty('width', {withValue: frame.size.width + 'px'});
|
|
940
|
+
controller.registerBuiltInProperty('height', {withValue: frame.size.height + 'px'});
|
|
941
|
+
this._layoutAnimator = controller.promise;
|
|
942
|
+
|
|
943
|
+
controller.promise.then(_ => {
|
|
944
|
+
this._layoutAnimator = undefined;
|
|
945
|
+
this.didSetFrame(frame);
|
|
946
|
+
|
|
947
|
+
if (boundsWillChange) {
|
|
948
|
+
this.boundsDidChangeToBounds(newBounds);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
this._frame = frame.copy();
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
|
|
956
|
+
// Assign the frame's coordinates to the node's positioning styles
|
|
957
|
+
this._frame = frame.copy();
|
|
958
|
+
if (BM_VIEW_USE_TRANSFORM) {
|
|
959
|
+
BMHook(this._node, {translateX: frame.origin.x + 'px', translateY: frame.origin.y + 'px'});
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
BMHook(this._node, {left: frame.origin.x + 'px', top: frame.origin.y + 'px'});
|
|
963
|
+
}
|
|
964
|
+
this._node.style.width = frame.size.width + 'px';
|
|
965
|
+
this._node.style.height = frame.size.height + 'px';
|
|
966
|
+
|
|
967
|
+
if (boundsWillChange) {
|
|
968
|
+
this.boundsDidChangeToBounds(newBounds);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
this.didSetFrame(frame);
|
|
973
|
+
|
|
974
|
+
},
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Animatable.
|
|
979
|
+
* A rectangle describing this view's size and position relative to its layout root view.
|
|
980
|
+
*
|
|
981
|
+
* Setting this value will cause the node managed by this view to have its position set to absolute and
|
|
982
|
+
* its size and position styles set to match the new frame's values. This will update the view's `frame` property
|
|
983
|
+
* accordingly.
|
|
984
|
+
*
|
|
985
|
+
* After setting this property, the position and size of the node managed by this view will be managed
|
|
986
|
+
* by CoreUI and should not be modified by outside means (e.g. through CSS).
|
|
987
|
+
*/
|
|
988
|
+
get frameRelativeToRootView() { // <BMRect>
|
|
989
|
+
let offsetPoint = BMPointMake();
|
|
990
|
+
let superview = this.superview;
|
|
991
|
+
while (superview) {
|
|
992
|
+
// The root view is by default set to the origin point
|
|
993
|
+
if (!superview.superview) break;
|
|
994
|
+
|
|
995
|
+
offsetPoint.x += superview.frame.origin.x;
|
|
996
|
+
offsetPoint.y += superview.frame.origin.y;
|
|
997
|
+
superview = superview.superview;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
let rootFrame = this.frame.copy();
|
|
1001
|
+
rootFrame.origin.x += offsetPoint.x;
|
|
1002
|
+
rootFrame.origin.y += offsetPoint.y;
|
|
1003
|
+
|
|
1004
|
+
return rootFrame;
|
|
1005
|
+
},
|
|
1006
|
+
set frameRelativeToRootView(frame) {
|
|
1007
|
+
let offsetPoint = BMPointMake();
|
|
1008
|
+
let superview = this.superview;
|
|
1009
|
+
while (superview) {
|
|
1010
|
+
// The root view is by default set to the origin point
|
|
1011
|
+
if (!superview.superview) break;
|
|
1012
|
+
|
|
1013
|
+
offsetPoint.x += superview.frame.origin.x;
|
|
1014
|
+
offsetPoint.y += superview.frame.origin.y;
|
|
1015
|
+
superview = superview.superview;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
let localFrame = frame.copy();
|
|
1019
|
+
localFrame.origin.x -= offsetPoint.x;
|
|
1020
|
+
localFrame.origin.y -= offsetPoint.y;
|
|
1021
|
+
|
|
1022
|
+
this.frame = localFrame;
|
|
1023
|
+
},
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Invoked by CoreUI after a frame was assigned to this view.
|
|
1027
|
+
*
|
|
1028
|
+
* Subclasses can override this method to perform any additional changes needed
|
|
1029
|
+
* for their content to fit the given frame.
|
|
1030
|
+
*
|
|
1031
|
+
* The default implementation does nothing.
|
|
1032
|
+
* @param frame <BMRect> The new frame.
|
|
1033
|
+
*/
|
|
1034
|
+
didSetFrame(frame) {
|
|
1035
|
+
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* A rectangle describing this view's size and position relative to its frame.
|
|
1040
|
+
*
|
|
1041
|
+
* The default value for this property is a rect with the origin set to (0, 0)
|
|
1042
|
+
* and the same size as this view's frame rectangle.
|
|
1043
|
+
*
|
|
1044
|
+
* Modifying this rectangle will typically cause the view's frame to change accordingly.
|
|
1045
|
+
*/
|
|
1046
|
+
get bounds() { // <BMRect>
|
|
1047
|
+
let bounds = this._frame.copy();
|
|
1048
|
+
bounds.origin = BMPointMake();
|
|
1049
|
+
|
|
1050
|
+
return bounds;
|
|
1051
|
+
},
|
|
1052
|
+
set bounds(bounds) {
|
|
1053
|
+
let oldBounds = this.bounds;
|
|
1054
|
+
|
|
1055
|
+
if (oldBounds.size.width != bounds.size.width || oldBounds.size.height != bounds.size.height) {
|
|
1056
|
+
let frame = this.frame.copy();
|
|
1057
|
+
frame.size = bounds.size.copy();
|
|
1058
|
+
|
|
1059
|
+
this.frame = frame;
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Invoked by CoreUI whenever this view's frame changes and its bounds are about to be updated as a result.
|
|
1065
|
+
* Subclasses can override this method to prepare for the new size.
|
|
1066
|
+
* The default implementation does nothing.
|
|
1067
|
+
* @param bounds <BMRect> The new bounds.
|
|
1068
|
+
*/
|
|
1069
|
+
boundsWillChangeToBounds(bounds) {
|
|
1070
|
+
|
|
1071
|
+
},
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Invoked by CoreUI whenever this view's frame has changed and its bounds have been updated as a result.
|
|
1075
|
+
* Subclasses can override this method to adjust their content to the new size.
|
|
1076
|
+
* The default implementation does nothing.
|
|
1077
|
+
* @param bounds <BMRect> The new bounds.
|
|
1078
|
+
*/
|
|
1079
|
+
boundsDidChangeToBounds(bounds) {
|
|
1080
|
+
|
|
1081
|
+
},
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Controls whether the layout managed by this view is in the left-to-right order.
|
|
1085
|
+
* The default implementation returns the global CoreUI left-to-right status.
|
|
1086
|
+
*/
|
|
1087
|
+
LTRLayout: YES, // <Boolean>
|
|
1088
|
+
|
|
1089
|
+
// #endregion
|
|
1090
|
+
|
|
1091
|
+
// #region Size Class Configuration
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Returns a variations object that can be serialized.
|
|
1095
|
+
*/
|
|
1096
|
+
get _serializedVariations() {
|
|
1097
|
+
let variations = {};
|
|
1098
|
+
|
|
1099
|
+
if (this._variations) {
|
|
1100
|
+
Object.keys(this._variations).forEach(key => {
|
|
1101
|
+
variations[key] = {};
|
|
1102
|
+
Object.keys(this._variations[key]).forEach(key2 => {
|
|
1103
|
+
if (key2 == 'sizeClass') return;
|
|
1104
|
+
variations[key][key2] = this._variations[key][key2];
|
|
1105
|
+
})
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return variations;
|
|
1110
|
+
},
|
|
1111
|
+
|
|
1112
|
+
set _serializedVariations(variations) {
|
|
1113
|
+
if (variations) {
|
|
1114
|
+
this._variations = variations;
|
|
1115
|
+
|
|
1116
|
+
for (let key in this._variations) {
|
|
1117
|
+
this._variations[key].sizeClass = BMLayoutSizeClass._layoutSizeClassForHashString(key);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* An array containing all of the properties supporting variations that have been registered for this view.
|
|
1124
|
+
*/
|
|
1125
|
+
_variableProperties: undefined, // <Dictionary<AnyObject>>
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* An object containing the active configuration of this view for the currently active size classes.
|
|
1129
|
+
*/
|
|
1130
|
+
_configuration: undefined, // <Object>
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Registers a property that supports variation on this view. Upon invoking this method, this view
|
|
1134
|
+
* object will gain several methods and properties depending on the name of the property.
|
|
1135
|
+
* If the name of the property duplicates an existing property of this view, this operation will raise
|
|
1136
|
+
* an error.
|
|
1137
|
+
*
|
|
1138
|
+
* For example, for a property named `color`, view will gain the following properties and methods:
|
|
1139
|
+
* * `color` represents the default value of the property, if no size class variation applies. The initial value of this property will be set to the current value of the property as it exists on the target object.
|
|
1140
|
+
* * `setColor(_, {forSizeClass})` can be used to register a variation of the property for the given size class.
|
|
1141
|
+
* * `removeColorVariationForSizeClass(_)` can be used to remove a variation of the property for the given size class.
|
|
1142
|
+
* * `hasColorVariationForSizeClass(_)` can be used to test whether the property has a variation for the given size class.
|
|
1143
|
+
*
|
|
1144
|
+
* Whenever the value of the property changes because of a size class variation, view will update the value of the
|
|
1145
|
+
* property on the object specified as the target.
|
|
1146
|
+
*
|
|
1147
|
+
* Note that variations for the property are retained if this property is unregistered. If the property is registered
|
|
1148
|
+
* again later, it will regain its previous variations.
|
|
1149
|
+
* @param name <String> The name of the property to register.
|
|
1150
|
+
* {
|
|
1151
|
+
* @param forTarget <AnyObject> The target object upon which the updated value will be set.
|
|
1152
|
+
* }
|
|
1153
|
+
*/
|
|
1154
|
+
registerVariablePropertyNamed(name, args) {
|
|
1155
|
+
if (this[name]) throw new Error('Unable to register the property ' + name + ' as a variable property. This property duplicates an existing property of this view.');
|
|
1156
|
+
|
|
1157
|
+
// Compute the capitalized name of the property, which is used in the various methods
|
|
1158
|
+
let capitalizedName = name[0].toUpperCase() + name.substring(1);
|
|
1159
|
+
let target = args.forTarget;
|
|
1160
|
+
|
|
1161
|
+
// Retain the name of this variable property
|
|
1162
|
+
this._variableProperties[name] = target;
|
|
1163
|
+
|
|
1164
|
+
// Create the methods and properties as required
|
|
1165
|
+
this[name] = target[name];
|
|
1166
|
+
this[`set${capitalizedName}`] = (value, args) => this._setVariation(value, {forProperty: name, inSizeClass: args.forSizeClass});
|
|
1167
|
+
this[`remove${capitalizedName}VariationForSizeClass`] = (sizeClass) => this._removeVariationForProperty(name, {inSizeClass: sizeClass});
|
|
1168
|
+
this[`has${capitalizedName}VariationForSizeClass`] = (sizeClass) => this._hasVariationForProperty(name, {inSizeClass: sizeClass});
|
|
1169
|
+
|
|
1170
|
+
},
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Unregisters a previously registered variable property.
|
|
1174
|
+
* If a property with the given name had not been previously registered as variable,
|
|
1175
|
+
* this method does nothing.
|
|
1176
|
+
* @param name <String> The name of the property to unregister.
|
|
1177
|
+
*/
|
|
1178
|
+
unregisterVariablePropertyNamed(name) {
|
|
1179
|
+
if (this._variableProperties[name]) {
|
|
1180
|
+
delete this._variableProperties[name];
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Invoked by CoreUI when the active size classes change for the view hierarchy
|
|
1186
|
+
* to which this view belongs. This causes the view to update its configuration
|
|
1187
|
+
* to match the new size classes, based on its variations.
|
|
1188
|
+
* @param sizeClasses <[BMLayoutSizeClass]> The active size classes.
|
|
1189
|
+
*/
|
|
1190
|
+
activeSizeClassesDidChange(sizeClasses) {
|
|
1191
|
+
this._updateConfiguration();
|
|
1192
|
+
},
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Invoked by CoreUI whenever size classes are invalidated or variations are introduced for this
|
|
1196
|
+
* view to cause the configuration used by this view to be updated.
|
|
1197
|
+
*/
|
|
1198
|
+
_updateConfiguration() {
|
|
1199
|
+
let baseConfiguration = {opacity: this._opacity, isVisible: this._isVisible, contentInsets: this._contentInsets, CSSClass: this._CSSClass};
|
|
1200
|
+
let viewport = this.viewport;
|
|
1201
|
+
|
|
1202
|
+
// Check which variations should be active and in which order
|
|
1203
|
+
for (let key in this._variations) {
|
|
1204
|
+
this._variations[key]._matchPriority = viewport.matchPriorityForSizeClass(this._variations[key].sizeClass);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
Object.keys(this._variations).map(key => this._variations[key]).sort((a, b) => b._matchPriority - a._matchPriority).forEach(variation => {
|
|
1208
|
+
if (!variation._matchPriority) return;
|
|
1209
|
+
|
|
1210
|
+
// Standard properties
|
|
1211
|
+
if ('opacity' in variation) baseConfiguration.opacity = variation.opacity;
|
|
1212
|
+
if ('isVisible' in variation) baseConfiguration.isVisible = variation.isVisible;
|
|
1213
|
+
if ('contentInsets' in variation) baseConfiguration.contentInsets = variation.contentInsets;
|
|
1214
|
+
if ('CSSClass' in variation) baseConfiguration.CSSClass = variation.CSSClass;
|
|
1215
|
+
|
|
1216
|
+
// Registered properties
|
|
1217
|
+
for (let key in this._variableProperties) {
|
|
1218
|
+
if (key in variation) {
|
|
1219
|
+
baseConfiguration[key] = variation[key];
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// Update the configuration to the newly computed configuration
|
|
1225
|
+
let oldConfiguration = this._configuration || baseConfiguration;
|
|
1226
|
+
this._configuration = baseConfiguration;
|
|
1227
|
+
|
|
1228
|
+
// Check how the configuration has changed and invalidate the layout accordingly
|
|
1229
|
+
if (oldConfiguration.opacity != baseConfiguration.opacity) {
|
|
1230
|
+
this._activeOpacity = baseConfiguration.opacity;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
if (oldConfiguration.isVisible != baseConfiguration.isVisible) {
|
|
1234
|
+
this._activeVisibility = baseConfiguration.isVisible;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (oldConfiguration.CSSClass != baseConfiguration.CSSClass) {
|
|
1238
|
+
this._activeCSSClass = baseConfiguration.CSSClass;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (!oldConfiguration.contentInsets.isEqualToInset(baseConfiguration.contentInsets)) {
|
|
1242
|
+
this._activeInsets = baseConfiguration.contentInsets;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
},
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Sets a generic variation for the given property in the given size class.
|
|
1249
|
+
* @param variation <AnyObject> The value to use.
|
|
1250
|
+
* {
|
|
1251
|
+
* @param forProperty <String> The name of the property that supports this variation.
|
|
1252
|
+
* @param inSizeClass <BMLayoutSizeClass> The layout size class to which the variation should apply.
|
|
1253
|
+
* }
|
|
1254
|
+
*/
|
|
1255
|
+
_setVariation(variation, args) {
|
|
1256
|
+
if (!this._variations[args.inSizeClass._hashString]) {
|
|
1257
|
+
this._variations[args.inSizeClass._hashString] = {sizeClass: args.inSizeClass};
|
|
1258
|
+
this.rootView._invalidatedSizeClasses = YES;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
this._variations[args.inSizeClass._hashString][args.forProperty] = variation;
|
|
1262
|
+
|
|
1263
|
+
// Update the configuration if this change affects the current size class
|
|
1264
|
+
if (this.viewport.matchesSizeClass(args.inSizeClass)) {
|
|
1265
|
+
this._updateConfiguration();
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Removes a generic property variation for the given size class.
|
|
1271
|
+
* If this view doesn't have a variation for the given property in this size class, this method does nothing.
|
|
1272
|
+
* @param property <String> The name of the property whose variation should be removed.
|
|
1273
|
+
* {
|
|
1274
|
+
* @param inSizeClass <BMLayoutSizeClass> The layout size class to which the variation should apply.
|
|
1275
|
+
* }
|
|
1276
|
+
*/
|
|
1277
|
+
_removeVariationForProperty(property, args) {
|
|
1278
|
+
if (this._variations[args.inSizeClass._hashString]) {
|
|
1279
|
+
// Delete the constant variation
|
|
1280
|
+
delete this._variations[args.inSizeClass._hashString][property];
|
|
1281
|
+
|
|
1282
|
+
// Remove the variations object for this size class if there are no other variations left
|
|
1283
|
+
if (Object.keys(this._variations[args.inSizeClass._hashString]).length == 1) {
|
|
1284
|
+
delete this._variations[args.inSizeClass._hashString];
|
|
1285
|
+
this.rootView._invalidatedSizeClasses = YES;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Update the configuration if this change affects the current size classes
|
|
1289
|
+
if (this.viewport.matchesSizeClass(args.inSizeClass)) {
|
|
1290
|
+
this._updateConfiguration();
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Used to verify if this view has a variation for the given property in the given size class.
|
|
1297
|
+
* @param property <String> The name of the property whose variation should be checked.
|
|
1298
|
+
* {
|
|
1299
|
+
* @param inSizeClass <BMLayoutSizeClass> The layout size class to which the variation applies.
|
|
1300
|
+
* }
|
|
1301
|
+
* @return <Boolean> `YES` if this view has a variation for the property in the given size class, `NO` otherwise.
|
|
1302
|
+
*/
|
|
1303
|
+
_hasVariationForProperty(property, args) {
|
|
1304
|
+
if (this._variations[args.inSizeClass._hashString]) {
|
|
1305
|
+
return property in this._variations[args.inSizeClass._hashString];
|
|
1306
|
+
}
|
|
1307
|
+
return NO;
|
|
1308
|
+
},
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Controls the edges between the content box and this view's bounds.
|
|
1313
|
+
* The value of this property is managed by CoreUI and matches the current configuration.
|
|
1314
|
+
*/
|
|
1315
|
+
__activeInsets: BMInsetMake(),
|
|
1316
|
+
|
|
1317
|
+
set _activeInsets(insets) {
|
|
1318
|
+
if (!insets) {
|
|
1319
|
+
this.__activeInsets = BMInsetMake();
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
this.__activeInsets = insets.copy();
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
insets = this.__activeInsets;
|
|
1326
|
+
/** @type {HTMLElement} */ const contentNode = this.contentNode;
|
|
1327
|
+
contentNode.style.paddingLeft = insets.left + 'px';
|
|
1328
|
+
contentNode.style.paddingTop = insets.top + 'px';
|
|
1329
|
+
contentNode.style.paddingRight = insets.right + 'px';
|
|
1330
|
+
contentNode.style.paddingBottom = insets.bottom + 'px';
|
|
1331
|
+
|
|
1332
|
+
// Modifying the padding will change the node's content size
|
|
1333
|
+
if (this.supportsIntrinsicSize) this.invalidateIntrinsicSize();
|
|
1334
|
+
},
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* Controls the edges between the content box and this view's bounds.
|
|
1338
|
+
*/
|
|
1339
|
+
_contentInsets: BMInsetMake(), // <BMInset>
|
|
1340
|
+
|
|
1341
|
+
get contentInsets() {
|
|
1342
|
+
return this._contentInsets.copy();
|
|
1343
|
+
},
|
|
1344
|
+
|
|
1345
|
+
set contentInsets(insets) {
|
|
1346
|
+
if (!insets) {
|
|
1347
|
+
this._contentInsets = BMInsetMake();
|
|
1348
|
+
}
|
|
1349
|
+
else {
|
|
1350
|
+
this._contentInsets = insets.copy();
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
this._updateConfiguration();
|
|
1354
|
+
},
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* Sets this view's `contentInsets` value for the given size class.
|
|
1358
|
+
* @param contentInsets <BMInset> `YES` if the view should be visible, `NO` otherwise.
|
|
1359
|
+
* {
|
|
1360
|
+
* @param forSizeClass <BMLayoutSizeClass> The layout size class to which the opacity should apply.
|
|
1361
|
+
* }
|
|
1362
|
+
*/
|
|
1363
|
+
setContentInsets(contentInsets, args) {
|
|
1364
|
+
return this._setVariation(contentInsets || BMInsetMake(), {forProperty: 'contentInsets', inSizeClass: args.forSizeClass});
|
|
1365
|
+
},
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Removes this views's `contentInsets` variation for the given size class.
|
|
1369
|
+
* If this view doesn't have an `contentInsets` variation for this size class, this method does nothing.
|
|
1370
|
+
* @param sizeClass <BMLayoutSizeClass> The layout size class.
|
|
1371
|
+
*/
|
|
1372
|
+
removeContentInsetsVariationForSizeClass(sizeClass) {
|
|
1373
|
+
return this._removeVariationForProperty('contentInsets', {inSizeClass: sizeClass});
|
|
1374
|
+
},
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Used to verify if this view has an `contentInsets` variation for the given size class.
|
|
1378
|
+
* @param sizeClass <BMLayoutSizeClass> The size class.
|
|
1379
|
+
* @return <Boolean> `YES` if this constraint has an `contentInsets` variation for the size class, `NO` otherwise.
|
|
1380
|
+
*/
|
|
1381
|
+
hasContentInsetsVariationForSizeClass(sizeClass) {
|
|
1382
|
+
return this._hasVariationForProperty('contentInsets', {inSizeClass: sizeClass});
|
|
1383
|
+
},
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Additional CSS classes to apply to this view's node. The classes specified in this property will be added
|
|
1387
|
+
* in addition to any other classes this view's node already defines.
|
|
1388
|
+
* The value of this property is managed by CoreUI and matches the current configuration.
|
|
1389
|
+
*/
|
|
1390
|
+
__activeCSSClass: '', // <string>
|
|
1391
|
+
set _activeCSSClass(CSSClass) {
|
|
1392
|
+
CSSClass = CSSClass || '';
|
|
1393
|
+
|
|
1394
|
+
const currentClasses = this.__activeCSSClass.split(' ');
|
|
1395
|
+
const newClasses = CSSClass.split(' ');
|
|
1396
|
+
|
|
1397
|
+
// Find the old classes that were removed and remove them
|
|
1398
|
+
for (const CSSClass of currentClasses) {
|
|
1399
|
+
// Skip empty class names (e.g. double spaces)
|
|
1400
|
+
if (!CSSClass) continue;
|
|
1401
|
+
if (!newClasses.includes(CSSClass)) {
|
|
1402
|
+
this.node.classList.remove(CSSClass);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Add the new classes
|
|
1407
|
+
for (const CSSClass of newClasses) {
|
|
1408
|
+
if (!CSSClass) continue;
|
|
1409
|
+
if (!currentClasses.includes(CSSClass)) {
|
|
1410
|
+
this.node.classList.add(CSSClass);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
this.__activeCSSClass = CSSClass;
|
|
1415
|
+
|
|
1416
|
+
// Changing the class may lead to changes that invalidate the node's intrinsic size
|
|
1417
|
+
if (this.supportsIntrinsicSize) this.invalidateIntrinsicSize();
|
|
1418
|
+
},
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Additional CSS classes to apply to this view's node. The classes specified in this property will be added
|
|
1422
|
+
* in addition to any other classes this view's node already has.
|
|
1423
|
+
*/
|
|
1424
|
+
_CSSClass: '', // <String>
|
|
1425
|
+
|
|
1426
|
+
get CSSClass() {
|
|
1427
|
+
return this._CSSClass;
|
|
1428
|
+
},
|
|
1429
|
+
set CSSClass(CSSClass) {
|
|
1430
|
+
this._CSSClass = CSSClass;
|
|
1431
|
+
this._updateConfiguration();
|
|
1432
|
+
},
|
|
1433
|
+
|
|
1434
|
+
/**
|
|
1435
|
+
* Sets this view's `CSSClass` value for the given size class.
|
|
1436
|
+
* @param CSSClass <String> The CSS class.
|
|
1437
|
+
* {
|
|
1438
|
+
* @param forSizeClass <BMLayoutSizeClass> The layout size class to which the CSS class should apply.
|
|
1439
|
+
* }
|
|
1440
|
+
*/
|
|
1441
|
+
setCSSClass(CSSClass, args) {
|
|
1442
|
+
return this._setVariation(CSSClass, {forProperty: 'CSSClass', inSizeClass: args.forSizeClass});
|
|
1443
|
+
},
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* Removes this views's `CSSClass` variation for the given size class.
|
|
1447
|
+
* If this view doesn't have an `CSSClass` variation for this size class, this method does nothing.
|
|
1448
|
+
* @param sizeClass <BMLayoutSizeClass> The layout size class.
|
|
1449
|
+
*/
|
|
1450
|
+
removeCSSClassVariationForSizeClass(sizeClass) {
|
|
1451
|
+
return this._removeVariationForProperty('CSSClass', {inSizeClass: sizeClass});
|
|
1452
|
+
},
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* Used to verify if this view has an `CSSClass` variation for the given size class.
|
|
1456
|
+
* @param sizeClass <BMLayoutSizeClass> The size class.
|
|
1457
|
+
* @return <Boolean> `YES` if this constraint has a `CSSClass` variation for the size class, `NO` otherwise.
|
|
1458
|
+
*/
|
|
1459
|
+
hasCSSClassVariationForSizeClass(sizeClass) {
|
|
1460
|
+
return this._hasVariationForProperty('CSSClass', {inSizeClass: sizeClass});
|
|
1461
|
+
},
|
|
1462
|
+
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* Animatable.
|
|
1467
|
+
* Controls the opacity of this view's node, regardless of configuration.
|
|
1468
|
+
* The value of this property is managed by CoreUI and always matches the currently active configuration.
|
|
1469
|
+
*/
|
|
1470
|
+
__activeOpacity: 1, // <Number>
|
|
1471
|
+
set _activeOpacity(opacity) {
|
|
1472
|
+
if (BMAnimationContextGetCurrent()) {
|
|
1473
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._node});
|
|
1474
|
+
controller.registerBuiltInProperty('opacity', {withValue: this._opacity});
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
this.__activeOpacity = opacity;
|
|
1478
|
+
this.node.style.opacity = opacity;
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* Returns the current opacity used by this view.
|
|
1484
|
+
*/
|
|
1485
|
+
get currentOpacity() { // <Number>
|
|
1486
|
+
return this.__activeOpacity;
|
|
1487
|
+
},
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Animatable.
|
|
1491
|
+
* Controls the opacity of this view's node.
|
|
1492
|
+
*/
|
|
1493
|
+
_opacity: 1, // <Number>
|
|
1494
|
+
|
|
1495
|
+
get opacity() {
|
|
1496
|
+
return this._opacity;
|
|
1497
|
+
},
|
|
1498
|
+
set opacity(opacity) {
|
|
1499
|
+
this._opacity = opacity;
|
|
1500
|
+
this._updateConfiguration();
|
|
1501
|
+
},
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* Sets this view's `opacity` value for the given size class.
|
|
1505
|
+
* @param opacity <Number> The opacity.
|
|
1506
|
+
* {
|
|
1507
|
+
* @param forSizeClass <BMLayoutSizeClass> The layout size class to which the opacity should apply.
|
|
1508
|
+
* }
|
|
1509
|
+
*/
|
|
1510
|
+
setOpacity(opacity, args) {
|
|
1511
|
+
return this._setVariation(opacity, {forProperty: 'opacity', inSizeClass: args.forSizeClass});
|
|
1512
|
+
},
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Removes this views's `opacity` variation for the given size class.
|
|
1516
|
+
* If this view doesn't have an `opacity` variation for this size class, this method does nothing.
|
|
1517
|
+
* @param sizeClass <BMLayoutSizeClass> The layout size class.
|
|
1518
|
+
*/
|
|
1519
|
+
removeOpacityVariationForSizeClass(sizeClass) {
|
|
1520
|
+
return this._removeVariationForProperty('opacity', {inSizeClass: sizeClass});
|
|
1521
|
+
},
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* Used to verify if this view has an `opacity` variation for the given size class.
|
|
1525
|
+
* @param sizeClass <BMLayoutSizeClass> The size class.
|
|
1526
|
+
* @return <Boolean> `YES` if this constraint has an `opacity` variation for the size class, `NO` otherwise.
|
|
1527
|
+
*/
|
|
1528
|
+
hasOpacityVariationForSizeClass(sizeClass) {
|
|
1529
|
+
return this._hasVariationForProperty('opacity', {inSizeClass: sizeClass});
|
|
1530
|
+
},
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Controls the visibility of this view's node, regardless of configuration.
|
|
1534
|
+
* The value of this property is managed by CoreUI and always matches the currently active configuration.
|
|
1535
|
+
*/
|
|
1536
|
+
__activeVisibility: YES, // <Boolean>
|
|
1537
|
+
|
|
1538
|
+
set _activeVisibility(visibility) {
|
|
1539
|
+
if (visibility != this.__activeVisibility) {
|
|
1540
|
+
const isVisible = this.isCurrentlyVisible;
|
|
1541
|
+
|
|
1542
|
+
this.__activeVisibility = visibility;
|
|
1543
|
+
if (visibility) {
|
|
1544
|
+
if (this.rootView._layoutEditor) {
|
|
1545
|
+
this.node.classList.remove('BMLayoutEditorInvisibleView');
|
|
1546
|
+
}
|
|
1547
|
+
else {
|
|
1548
|
+
this.node.style.display = 'block';
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
if (!isVisible) {
|
|
1552
|
+
this.viewDidBecomeVisible();
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
else {
|
|
1556
|
+
if (this.rootView._layoutEditor) {
|
|
1557
|
+
this.node.classList.add('BMLayoutEditorInvisibleView');
|
|
1558
|
+
}
|
|
1559
|
+
else {
|
|
1560
|
+
this.node.style.display = 'none';
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
if (!isVisible) {
|
|
1564
|
+
this.viewDidBecomeInvisible();
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// Propagate the visibility status down to the subviews
|
|
1569
|
+
for (const subview of this._subviews) {
|
|
1570
|
+
subview._parentVisible = visibility;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// Changing visibility invalidates the node's intrinsic size
|
|
1574
|
+
this.invalidateIntrinsicSize();
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
|
|
1578
|
+
get _activeVisibility() {
|
|
1579
|
+
return this.__activeVisibility;
|
|
1580
|
+
},
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* Set to `NO` when any ancestor of this view is hidden.
|
|
1584
|
+
*/
|
|
1585
|
+
__parentVisible: YES, // <Boolean>
|
|
1586
|
+
|
|
1587
|
+
get _parentVisible() {
|
|
1588
|
+
return this.__parentVisible;
|
|
1589
|
+
},
|
|
1590
|
+
|
|
1591
|
+
set _parentVisible(visible) {
|
|
1592
|
+
const isVisible = visible && this.__activeVisibility;
|
|
1593
|
+
const isCurrentlyVisible = this.isCurrentlyVisible;
|
|
1594
|
+
|
|
1595
|
+
// If the visibility status changes, propagate it down to the subviews
|
|
1596
|
+
if (isVisible != this.__parentVisible) {
|
|
1597
|
+
for (const subview of this._subviews) {
|
|
1598
|
+
subview._parentVisible = visible;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
this.__parentVisible = isVisible;
|
|
1603
|
+
|
|
1604
|
+
if (this.isCurrentlyVisible != isCurrentlyVisible) {
|
|
1605
|
+
if (this.isCurrentlyVisible) {
|
|
1606
|
+
this.viewDidBecomeVisible();
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
this.viewDidBecomeInvisible();
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
},
|
|
1613
|
+
|
|
1614
|
+
/**
|
|
1615
|
+
* Returns `YES` if this view is visible in the current configuration, `NO` otherwise.
|
|
1616
|
+
* This will also return `NO` if any ancestors are hidden.
|
|
1617
|
+
*/
|
|
1618
|
+
get isCurrentlyVisible() { // <Boolean>
|
|
1619
|
+
return this.__activeVisibility && this.__parentVisible;
|
|
1620
|
+
},
|
|
1621
|
+
|
|
1622
|
+
/**
|
|
1623
|
+
* @protected
|
|
1624
|
+
* Invoked when this view becomes visible. Subclasses overriding this method should
|
|
1625
|
+
* invoke the superclass method at some point in their implementation.
|
|
1626
|
+
*/
|
|
1627
|
+
viewDidBecomeVisible() {
|
|
1628
|
+
// If this view supports an intrinsic size, invalidate it upon becoming visible
|
|
1629
|
+
if (this.supportsIntrinsicSize) {
|
|
1630
|
+
this.invalidateIntrinsicSize();
|
|
1631
|
+
}
|
|
1632
|
+
},
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* @protected
|
|
1636
|
+
* Invoked when this view becomes invisible. Subclasses overriding this method should
|
|
1637
|
+
* invoke the superclass method at some point in their implementation.
|
|
1638
|
+
*/
|
|
1639
|
+
viewDidBecomeInvisible() {
|
|
1640
|
+
|
|
1641
|
+
},
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* Controls the visibility of this view. Invisible views still participate in layout operations as usual,
|
|
1645
|
+
* but views that support automatic intrinsic size may report an intrinsic size of `[0, 0]` while they are invisible.
|
|
1646
|
+
*
|
|
1647
|
+
* Additionally, views that are not visible cannot be interacted with.
|
|
1648
|
+
*/
|
|
1649
|
+
_isVisible: YES, // <Boolean>
|
|
1650
|
+
|
|
1651
|
+
get isVisible() {
|
|
1652
|
+
return this._isVisible;
|
|
1653
|
+
},
|
|
1654
|
+
set isVisible(isVisible) {
|
|
1655
|
+
this._isVisible = isVisible
|
|
1656
|
+
this._updateConfiguration();
|
|
1657
|
+
},
|
|
1658
|
+
|
|
1659
|
+
/**
|
|
1660
|
+
* Sets this view's `isVisible` value for the given size class.
|
|
1661
|
+
* @param isVisible <Boolean> `YES` if the view should be visible, `NO` otherwise.
|
|
1662
|
+
* {
|
|
1663
|
+
* @param forSizeClass <BMLayoutSizeClass> The layout size class to which the opacity should apply.
|
|
1664
|
+
* }
|
|
1665
|
+
*/
|
|
1666
|
+
setIsVisible(isVisible, args) {
|
|
1667
|
+
return this._setVariation(isVisible, {forProperty: 'isVisible', inSizeClass: args.forSizeClass});
|
|
1668
|
+
},
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* Removes this views's `isVisible` variation for the given size class.
|
|
1672
|
+
* If this view doesn't have an `isVisible` variation for this size class, this method does nothing.
|
|
1673
|
+
* @param sizeClass <BMLayoutSizeClass> The layout size class.
|
|
1674
|
+
*/
|
|
1675
|
+
removeIsVisibleVariationForSizeClass(sizeClass) {
|
|
1676
|
+
return this._removeVariationForProperty('isVisible', {inSizeClass: sizeClass});
|
|
1677
|
+
},
|
|
1678
|
+
|
|
1679
|
+
/**
|
|
1680
|
+
* Used to verify if this view has an `isVisible` variation for the given size class.
|
|
1681
|
+
* @param sizeClass <BMLayoutSizeClass> The size class.
|
|
1682
|
+
* @return <Boolean> `YES` if this constraint has an `isVisible` variation for the size class, `NO` otherwise.
|
|
1683
|
+
*/
|
|
1684
|
+
hasIsVisibleVariationForSizeClass(sizeClass) {
|
|
1685
|
+
return this._hasVariationForProperty('isVisible', {inSizeClass: sizeClass});
|
|
1686
|
+
},
|
|
1687
|
+
|
|
1688
|
+
// #endregion
|
|
1689
|
+
|
|
1690
|
+
// #region Intrinsic Size
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* Used by CoreUI to determine if this view's intrinsic size should match the intrinsic size reported by its node element.
|
|
1694
|
+
* When this getter returns <code>YES</code>, CoreUI will measure the view's node to determine its intrinsic size.
|
|
1695
|
+
* Subclasses that can make use of automatic intrinsic size do not need to override the getter for <code>intrinsicSize</code>
|
|
1696
|
+
* to support intrinsic sizes.
|
|
1697
|
+
*
|
|
1698
|
+
* Subclasses that do not support intrinsic sizes or need to perform their own calculations to supply an intrinsic size
|
|
1699
|
+
* should override this getter and return <code>NO</code>.
|
|
1700
|
+
*
|
|
1701
|
+
* The default implementation returns <code>NO</code>.
|
|
1702
|
+
*/
|
|
1703
|
+
_supportsAutomaticIntrinsicSize: NO, // <Boolean>
|
|
1704
|
+
get supportsAutomaticIntrinsicSize() {
|
|
1705
|
+
return this._supportsAutomaticIntrinsicSize;
|
|
1706
|
+
},
|
|
1707
|
+
set supportsAutomaticIntrinsicSize(supports) {
|
|
1708
|
+
this._supportsAutomaticIntrinsicSize = supports;
|
|
1709
|
+
},
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* Used by CoreUI to determine if this view can provide an intrinsic size.
|
|
1713
|
+
*
|
|
1714
|
+
* Subclasses that explicitly support or do not support intrinsic sizes should override this value and
|
|
1715
|
+
* return the appropriate. This is used by CoreUI to determine whether the constraints affecting this view
|
|
1716
|
+
* can fully define its layout attributes.
|
|
1717
|
+
*
|
|
1718
|
+
* The default implementation returns the same value as <code>supportsAutomaticIntrinsicSize</code>.
|
|
1719
|
+
*/
|
|
1720
|
+
get supportsIntrinsicSize() { // <Boolean>
|
|
1721
|
+
return this.supportsAutomaticIntrinsicSize;
|
|
1722
|
+
},
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* Will be set to a number that represents the width that will be assigned to this view
|
|
1726
|
+
* after the current layout pass.
|
|
1727
|
+
* Outside of a layout pass or before this view has been assigned a width, this property
|
|
1728
|
+
* will be set to <code>undefined</code>. Subclasses that override <code>intrinsicSize</code> should
|
|
1729
|
+
* check the value of this property when computing their intrinsic size and adjust it appropriately
|
|
1730
|
+
* if this value is not <code>undefined</code>.
|
|
1731
|
+
*/
|
|
1732
|
+
_requiredWidth: undefined, // <Number>
|
|
1733
|
+
get requiredWidth() {
|
|
1734
|
+
return this._requiredWidth;
|
|
1735
|
+
},
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* Set to `YES` when views invalidate their intrinsic size and the layout engine must
|
|
1739
|
+
* measure it again.
|
|
1740
|
+
*/
|
|
1741
|
+
_needsIntrinsicSizeMeasurement: YES, // <Boolean>
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* Returns `YES` if this view or any of its descendants have had their intrinsic size invalidated.
|
|
1745
|
+
*/
|
|
1746
|
+
get needsIntrinsicSizeMeasurement() { // <Boolean>
|
|
1747
|
+
if (this._needsIntrinsicSizeMeasurement && this.supportsAutomaticIntrinsicSize) return YES;
|
|
1748
|
+
|
|
1749
|
+
if (!this._hasDeterministicSize && this._preferredIntrinsicSize && this._intrinsicSize && !this._preferredIntrinsicSize.isEqualToSize(this._instrinsicSize)) return YES;
|
|
1750
|
+
|
|
1751
|
+
let subviewsLength = this._subviews.length;
|
|
1752
|
+
for (let i = 0; i < subviewsLength; i++) {
|
|
1753
|
+
if (this._subviews[i].needsIntrinsicSizeMeasurement) return YES;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
return NO;
|
|
1757
|
+
},
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* Should be invoked when this view's intrinsic size is no longer valid and should be measured again
|
|
1761
|
+
* by the layout engine during the next layout pass. This implicitly invalidates this view's layout as well,
|
|
1762
|
+
* causing a layout pass to run before the next animation frame.
|
|
1763
|
+
*
|
|
1764
|
+
* To prevent visual artifacts that might be caused by the content updating without the view's bounds, CoreUI
|
|
1765
|
+
* will attempt to perform the next layout pass before the next animation frame renders.
|
|
1766
|
+
*/
|
|
1767
|
+
invalidateIntrinsicSize() {
|
|
1768
|
+
this._preferredIntrinsicSize = undefined;
|
|
1769
|
+
this._needsIntrinsicSizeMeasurement = YES;
|
|
1770
|
+
|
|
1771
|
+
const rootView = this.rootView;
|
|
1772
|
+
rootView._needsLayout = YES;
|
|
1773
|
+
rootView._scheduleImmediateLayout();
|
|
1774
|
+
},
|
|
1775
|
+
|
|
1776
|
+
/**
|
|
1777
|
+
* The last measured intrinsic size for this view, if available.
|
|
1778
|
+
*/
|
|
1779
|
+
_intrinsicSize: undefined, // <BMSize, nullable>
|
|
1780
|
+
|
|
1781
|
+
/**
|
|
1782
|
+
* A size that represents this view's preferred intrinsic size. This is the intrinsic size returned by the view
|
|
1783
|
+
* during the first layout pass, before having been assigned a width. It is used to determine if a new intrinsic size should
|
|
1784
|
+
* be requested for this view during subsequent layout passes so this view has a chance to adjust to its new size restrictions.
|
|
1785
|
+
*
|
|
1786
|
+
* This value is automatically managed by CoreUI.
|
|
1787
|
+
*/
|
|
1788
|
+
_preferredIntrinsicSize: undefined, // <BMSize, nullable>
|
|
1789
|
+
|
|
1790
|
+
/**
|
|
1791
|
+
* Invoked internally by CoreUI to obtain and cache the intrinsic size for this view.
|
|
1792
|
+
*/
|
|
1793
|
+
_getIntrinsicSize() {
|
|
1794
|
+
let result;
|
|
1795
|
+
if (this._needsIntrinsicSizeMeasurement) {
|
|
1796
|
+
if (this.requiredWidth) {
|
|
1797
|
+
result = this._instrinsicSize = this.intrinsicSize;
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
result = this._instrinsicSize = this._preferredIntrinsicSize = this.intrinsicSize;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
else if (this.requiredWidth) {
|
|
1804
|
+
if (this._intrinsicSize && !this._intrinsicSize.isEqualToSize(this._preferredIntrinsicSize)) {
|
|
1805
|
+
result = this._instrinsicSize = this.intrinsicSize;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
if (!result) result = this._instrinsicSize;
|
|
1810
|
+
|
|
1811
|
+
return result;
|
|
1812
|
+
},
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* This view's intrinsic size. If this view does not have an intrinsic size, this property's value will be
|
|
1816
|
+
* undefined.
|
|
1817
|
+
* The default implementation returns undefined, unless `supportsAutomaticIntrinsicSize` is set to YES.
|
|
1818
|
+
* Whenever a view's intrinsic size changes, for example if the content from which it is derived changes,
|
|
1819
|
+
* the view should invoke `invalidateIntrinsicSize()` to cause the layout engine to measure the view when
|
|
1820
|
+
* performing the next layout pass.
|
|
1821
|
+
*
|
|
1822
|
+
* Subclasses that support intrinsic sizes should override this getter to return the correct intrinsic
|
|
1823
|
+
* size corresponding to the view's contents.
|
|
1824
|
+
*
|
|
1825
|
+
* The CoreUI layout engine will invoke this getter twice during a layout pass. The first time it will do so
|
|
1826
|
+
* to obtain the overall size that this view would like to have. Afterwards it will compute the horizontal
|
|
1827
|
+
* layout and assign a width to this view. It will then invoke this getter again to verify if the intrinsic
|
|
1828
|
+
* size may have changed in response to the new width requirement.
|
|
1829
|
+
* Subclasses should check the value of the `requiredWidth` property to check if they have been assigned a width when computing
|
|
1830
|
+
* their intrisic sizes.
|
|
1831
|
+
*/
|
|
1832
|
+
get intrinsicSize() { // <BMSize, nullable>
|
|
1833
|
+
if (this.supportsAutomaticIntrinsicSize) {
|
|
1834
|
+
if (this._needsIntrinsicSizeMeasurement) {
|
|
1835
|
+
let node = this._node;
|
|
1836
|
+
|
|
1837
|
+
// +1 is used because text nodes often have fractional values, but scrollWidth truncates the
|
|
1838
|
+
// decimal part so the text ends up overflowing into a second line.
|
|
1839
|
+
// getBoundingClientRect normally returns these fractional values, but it is affected
|
|
1840
|
+
// by transforms which makes it unreliable for intrinsic size calculations
|
|
1841
|
+
this._intrinsicSize = BMSizeMake((node.offsetWidth - node.clientWidth) + node.scrollWidth + 1,
|
|
1842
|
+
(node.offsetHeight - node.clientHeight) + node.offsetHeight);
|
|
1843
|
+
|
|
1844
|
+
|
|
1845
|
+
if (BM_VIEW_DEBUG_AUTOMATIC_INTRINSIC_SIZE) {
|
|
1846
|
+
let flash = document.createElement('div');
|
|
1847
|
+
flash.style.cssText = 'position: absolute; z-index: 999999999; top: 0px; left: 0px; width: 100%; height: 100%; background-color: red; pointer-events: none;';
|
|
1848
|
+
node.appendChild(flash);
|
|
1849
|
+
requestAnimationFrame(() => flash.remove());
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return this._intrinsicSize;
|
|
1853
|
+
}
|
|
1854
|
+
return undefined;
|
|
1855
|
+
},
|
|
1856
|
+
|
|
1857
|
+
/**
|
|
1858
|
+
* Controls how much this view will resist being compressed below its intrinsic size in a layout
|
|
1859
|
+
* managed by CoreUI. This generates two implicit constraints, one for width and one for height,
|
|
1860
|
+
* that specify that this view's size should be greater than or equal to its intrinsic size.
|
|
1861
|
+
* That constraint will have its priority set to this value.
|
|
1862
|
+
* A value of <code>BMLayoutConstraintPriorityRequired</code> will ensure that this view's content
|
|
1863
|
+
* will never become compressed in a valid layout.
|
|
1864
|
+
* This value has no effect if this view does not provide an intrinsic size.
|
|
1865
|
+
*/
|
|
1866
|
+
_compressionResistance: 750, // <Number>
|
|
1867
|
+
get compressionResistance() {
|
|
1868
|
+
return this._compressionResistance;
|
|
1869
|
+
},
|
|
1870
|
+
set compressionResistance(resistance) {
|
|
1871
|
+
this._compressionResistance = resistance;
|
|
1872
|
+
this._widthCompressionConstraint = undefined;
|
|
1873
|
+
this._heightCompressionConstraint = undefined;
|
|
1874
|
+
this.needsLayout = YES;
|
|
1875
|
+
this.rootView._invalidatedConstraints = YES;
|
|
1876
|
+
},
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* Controls how much this view will resist being expanded beyond its intrinsic size in a layout
|
|
1880
|
+
* managed by CoreUI. This generates two implicit constraints, one for width and one for height,
|
|
1881
|
+
* that specify that this view's size should be less than or equal to its intrinsic size.
|
|
1882
|
+
* That constraint will have its priority set to this value.
|
|
1883
|
+
* A value of <code>BMLayoutConstraintPriorityRequired</code> will ensure that this view's size will never
|
|
1884
|
+
* be larger than its intrinsic size in a valid layout.
|
|
1885
|
+
* This value has no effect if this view does not provide an intrinsic size.
|
|
1886
|
+
*/
|
|
1887
|
+
_expansionResistance: 250, // <Number>
|
|
1888
|
+
get expansionResistance() {
|
|
1889
|
+
return this._expansionResistance;
|
|
1890
|
+
},
|
|
1891
|
+
set expansionResistance(resistance) {
|
|
1892
|
+
this._expansionResistance = resistance;
|
|
1893
|
+
this._widthExpansionConstraint = undefined;
|
|
1894
|
+
this._heightExpansionConstraint = undefined;
|
|
1895
|
+
this.needsLayout = YES;
|
|
1896
|
+
this.rootView._invalidatedConstraints = YES;
|
|
1897
|
+
},
|
|
1898
|
+
|
|
1899
|
+
// #endregion
|
|
1900
|
+
|
|
1901
|
+
// #region Size Classes
|
|
1902
|
+
|
|
1903
|
+
/**
|
|
1904
|
+
* Set to `YES` if this view should update its internal size classes structure
|
|
1905
|
+
* during the next layout pass.
|
|
1906
|
+
*/
|
|
1907
|
+
_invalidatedSizeClasses: NO, // <Boolean>
|
|
1908
|
+
|
|
1909
|
+
/**
|
|
1910
|
+
* Contains the size classes that affect the layout or properties of this view hierarchy.
|
|
1911
|
+
*/
|
|
1912
|
+
_sizeClasses: undefined, // <Set<BMLayoutSizeClass>>
|
|
1913
|
+
|
|
1914
|
+
/**
|
|
1915
|
+
* Contains the size classes that match the view's current viewport.
|
|
1916
|
+
*/
|
|
1917
|
+
_matchingSizeClasses: undefined, // <Set<BMLayoutSizeClass>>
|
|
1918
|
+
|
|
1919
|
+
/**
|
|
1920
|
+
* Invoked internally by CoreUI when the size classes that affect this view hierarchy have changed.
|
|
1921
|
+
* This causes the root view to check how size classes have changed and may, depending on what changes, trigger
|
|
1922
|
+
* a complete layout pass.
|
|
1923
|
+
*/
|
|
1924
|
+
_updateSizeClasses() {
|
|
1925
|
+
this._sizeClasses = new Set;
|
|
1926
|
+
|
|
1927
|
+
// Retrieve and enumerate all of the subviews within this view hierarchy
|
|
1928
|
+
this.allSubviews.forEach(subview => {
|
|
1929
|
+
// Enumerate the variations supported by this view
|
|
1930
|
+
for (let sizeClassHashString in subview._variations) {
|
|
1931
|
+
// For each variation, create an entry in the size classes map if there isn't one - as identical size classes are
|
|
1932
|
+
// also guaranteed to be the same instance, using the hash string is not required
|
|
1933
|
+
let sizeClass = subview._variations[sizeClassHashString].sizeClass;
|
|
1934
|
+
if (!this._sizeClasses.has(sizeClass)) {
|
|
1935
|
+
this._sizeClasses.add(sizeClass);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
// Retrieve and enumerate all of the constraints within this view hierarchy
|
|
1941
|
+
this.allConstraints.forEach(constraint => {
|
|
1942
|
+
// Enumerate the variations supported by this constraint
|
|
1943
|
+
for (let sizeClassHashString in constraint._variations) {
|
|
1944
|
+
// For each variation, create an entry in the size classes map if there isn't one - as identical size classes are
|
|
1945
|
+
// also guaranteed to be the same instance, using the hash string is not required
|
|
1946
|
+
let sizeClass = constraint._variations[sizeClassHashString].sizeClass;
|
|
1947
|
+
if (!this._sizeClasses.has(sizeClass)) {
|
|
1948
|
+
this._sizeClasses.add(sizeClass);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
},
|
|
1953
|
+
|
|
1954
|
+
/**
|
|
1955
|
+
* The viewport used by this view hierarchy. In most cases, the value of this property is identical across
|
|
1956
|
+
* view hierarchies and matches the actual viewport.
|
|
1957
|
+
*/
|
|
1958
|
+
_viewport: undefined, // <BMViewport>
|
|
1959
|
+
get viewport() {
|
|
1960
|
+
if (this.isRootView) {
|
|
1961
|
+
!this._viewport && this._updateViewport();
|
|
1962
|
+
return this._requiredViewport || this._viewport;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
return this.rootView.viewport;
|
|
1966
|
+
},
|
|
1967
|
+
|
|
1968
|
+
/**
|
|
1969
|
+
* Used by the layout editor to force this view to appear as if the given viewport had been active.
|
|
1970
|
+
*/
|
|
1971
|
+
_requiredViewport: undefined, // <BMViewport>
|
|
1972
|
+
|
|
1973
|
+
/**
|
|
1974
|
+
* Used internally by CoreUI. Computes and returns the current viewport.
|
|
1975
|
+
*/
|
|
1976
|
+
get _currentViewport() {
|
|
1977
|
+
if (this._requiredViewport) return this._requiredViewport;
|
|
1978
|
+
|
|
1979
|
+
let viewport = new BMViewport;
|
|
1980
|
+
viewport.init();
|
|
1981
|
+
|
|
1982
|
+
viewport._width = window.innerWidth;
|
|
1983
|
+
viewport._height = window.innerHeight;
|
|
1984
|
+
viewport._diagonal = Math.sqrt(Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2));
|
|
1985
|
+
viewport._orientation = viewport._width >= viewport._height ? BMLayoutOrientation.Landscape : BMLayoutOrientation.Portrait;
|
|
1986
|
+
viewport._surfaceArea = viewport._width * viewport._height;
|
|
1987
|
+
|
|
1988
|
+
return viewport;
|
|
1989
|
+
},
|
|
1990
|
+
|
|
1991
|
+
/**
|
|
1992
|
+
* Invoked by CoreUI whenever the global layout variables have been modified.
|
|
1993
|
+
* This causes the view to invalidate its layout if this change affects the current configuration.
|
|
1994
|
+
*/
|
|
1995
|
+
_layoutVariablesDidUpdate() {
|
|
1996
|
+
// If this view has not had a chance to compute its first layout, there is no
|
|
1997
|
+
// need to perform any additional action as it will udpate its layout variable
|
|
1998
|
+
// values during the first layout passs
|
|
1999
|
+
if (!this._viewport) return;
|
|
2000
|
+
|
|
2001
|
+
const currentLayoutVariables = this._layoutVariables || {};
|
|
2002
|
+
|
|
2003
|
+
// Update the values of the layout variables
|
|
2004
|
+
this._layoutVariableValuesForViewport(this._viewport);
|
|
2005
|
+
|
|
2006
|
+
// Verify if any layout variables have changed
|
|
2007
|
+
let layoutVariablesDidChange = NO;
|
|
2008
|
+
for (let key in this._layoutVariables) {
|
|
2009
|
+
if (currentLayoutVariables[key] != this._layoutVariables[key]) {
|
|
2010
|
+
layoutVariablesDidChange = YES;
|
|
2011
|
+
break;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
if (layoutVariablesDidChange) {
|
|
2016
|
+
// If the layout variable values did change the constraints
|
|
2017
|
+
// have to be notified in case they use layout variables and should update their configuration accordingly
|
|
2018
|
+
this.allConstraints.forEach(constraint => constraint.layoutVariablesDidChange());
|
|
2019
|
+
}
|
|
2020
|
+
},
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* Invoked internally to update the viewport characteristics used by this view hierarchy.
|
|
2024
|
+
*/
|
|
2025
|
+
_updateViewport() {
|
|
2026
|
+
let viewport = this._currentViewport;
|
|
2027
|
+
|
|
2028
|
+
this._viewport = viewport;
|
|
2029
|
+
|
|
2030
|
+
let currentLayoutVariables = this._layoutVariables || {};
|
|
2031
|
+
|
|
2032
|
+
// Update the values of the layout variables
|
|
2033
|
+
this._layoutVariableValuesForViewport(viewport);
|
|
2034
|
+
|
|
2035
|
+
// Verify if any layout variables have changed
|
|
2036
|
+
let layoutVariablesDidChange = NO;
|
|
2037
|
+
for (let key in this._layoutVariables) {
|
|
2038
|
+
if (currentLayoutVariables[key] != this._layoutVariables[key]) {
|
|
2039
|
+
layoutVariablesDidChange = YES;
|
|
2040
|
+
break;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
// Create a list of the size classes that match this viewport
|
|
2045
|
+
let matchingSizeClasses = new Set;
|
|
2046
|
+
for (let sizeClass of this._sizeClasses.keys()) {
|
|
2047
|
+
if (viewport.matchesSizeClass(sizeClass)) {
|
|
2048
|
+
matchingSizeClasses.add(sizeClass);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
if (this._matchingSizeClasses) {
|
|
2053
|
+
// Compare this new list to the previously matching size classes
|
|
2054
|
+
let sizeClassesDidUpdate = NO;
|
|
2055
|
+
if (this._matchingSizeClasses.size != matchingSizeClasses.size) {
|
|
2056
|
+
sizeClassesDidUpdate = YES;
|
|
2057
|
+
}
|
|
2058
|
+
else for (let sizeClass of this._matchingSizeClasses) {
|
|
2059
|
+
if (!matchingSizeClasses.has(sizeClass)) {
|
|
2060
|
+
sizeClassesDidUpdate = YES;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// If the size classes did change, notify all of the observers that the new set of
|
|
2065
|
+
// size classes has become active
|
|
2066
|
+
if (sizeClassesDidUpdate) {
|
|
2067
|
+
this._matchingSizeClasses = matchingSizeClasses;
|
|
2068
|
+
let matchingSizeClassesArray = Array.from(matchingSizeClasses);
|
|
2069
|
+
|
|
2070
|
+
// Notify the constraints and subviews that the size classes have been updated
|
|
2071
|
+
this.allConstraints.forEach(constraint => constraint.activeSizeClassesDidChange(matchingSizeClassesArray));
|
|
2072
|
+
this.allSubviews.forEach(subview => subview.activeSizeClassesDidChange(matchingSizeClassesArray));
|
|
2073
|
+
}
|
|
2074
|
+
else if (layoutVariablesDidChange) {
|
|
2075
|
+
// If the size classes did not change, but the layout variables did, the constraints
|
|
2076
|
+
// have to be notified nevertheless in case they use layout variables
|
|
2077
|
+
this.allConstraints.forEach(constraint => constraint.layoutVariablesDidChange());
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
else {
|
|
2081
|
+
// If there were no previously matching size classes, notify all of the observers that the
|
|
2082
|
+
// size classes have become active
|
|
2083
|
+
this._matchingSizeClasses = matchingSizeClasses;
|
|
2084
|
+
let matchingSizeClassesArray = Array.from(matchingSizeClasses);
|
|
2085
|
+
|
|
2086
|
+
// Notify the constraints that the size classes have been updated
|
|
2087
|
+
this.allConstraints.forEach(constraint => constraint.activeSizeClassesDidChange(matchingSizeClassesArray));
|
|
2088
|
+
this.allSubviews.forEach(subview => subview.activeSizeClassesDidChange(matchingSizeClassesArray));
|
|
2089
|
+
}
|
|
2090
|
+
},
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* Returns the current value of the given layout variable, if it exists.
|
|
2094
|
+
* @param variable <String> The name of the layout variable. Optionally, the name of the layout variable may be prefixed with a minus sign,
|
|
2095
|
+
* which will cause this method to return the negated value of the layout variable.
|
|
2096
|
+
* @return <Number, nullable> The current value of that layout variable, or `0` if no such layout variable had been defined.
|
|
2097
|
+
*/
|
|
2098
|
+
valueForLayoutVariable(variable) {
|
|
2099
|
+
const rootView = this.rootView;
|
|
2100
|
+
if (rootView != this) return rootView.valueForLayoutVariable(variable);
|
|
2101
|
+
|
|
2102
|
+
if (variable.charAt(0) == '-') {
|
|
2103
|
+
return -(this._layoutVariables[variable.substring(1)] || 0);
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
return this._layoutVariables[variable] || 0;
|
|
2107
|
+
},
|
|
2108
|
+
|
|
2109
|
+
// #endregion
|
|
2110
|
+
|
|
2111
|
+
// #region Constraints and Cassowary Variables
|
|
2112
|
+
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* An internal list of layout constraints that affect this view. This array will contain both constraints having
|
|
2116
|
+
* this view as the source view and constraints having this view as the target view. It also contains constraints
|
|
2117
|
+
* that have been registered for this view but are marked inactive.
|
|
2118
|
+
*/
|
|
2119
|
+
_constraints: undefined, // <[BMLayoutConstraint]>
|
|
2120
|
+
|
|
2121
|
+
/**
|
|
2122
|
+
* An internal list of Cassowary variables used by the constraints affecting this view.
|
|
2123
|
+
* These are automatically generated based upon the active constraints affecting this view.
|
|
2124
|
+
*/
|
|
2125
|
+
_variables: undefined, // <Object<String, kiwi.Variable>>
|
|
2126
|
+
|
|
2127
|
+
/**
|
|
2128
|
+
* Finds and returns the constraint with the given identifier within this view or any of its descendants.
|
|
2129
|
+
* @param identifier <String> The identifier of the constraint to find.
|
|
2130
|
+
* @return <BMLayoutConstraint, nullable> The requested constraint if it was found, or `undefined` otherwise.
|
|
2131
|
+
*/
|
|
2132
|
+
constraintWithIdentifier(identifier) {
|
|
2133
|
+
for (let constraint of this._constraints) {
|
|
2134
|
+
if (constraint.identifier == identifier) return constraint;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
for (let subview of this._subviews) {
|
|
2138
|
+
let constraint = subview.constraintWithIdentifier(identifier);
|
|
2139
|
+
if (constraint) return constraint;
|
|
2140
|
+
}
|
|
2141
|
+
},
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* An array containing all of the layout constraints that affect this view or any of its descendants, regardless of
|
|
2145
|
+
* whether they are active or not or whether the views they affect have deterministic constraints or not.
|
|
2146
|
+
*/
|
|
2147
|
+
get allConstraints() { // <[BMLayoutConstraint]>
|
|
2148
|
+
let constraints = this._constraints.slice();
|
|
2149
|
+
|
|
2150
|
+
this._subviews.forEach(subview => {
|
|
2151
|
+
constraints = constraints.concat(subview.allConstraints);
|
|
2152
|
+
});
|
|
2153
|
+
|
|
2154
|
+
return constraints;
|
|
2155
|
+
},
|
|
2156
|
+
|
|
2157
|
+
/**
|
|
2158
|
+
* A list of layout constraints that currently affect this view or its descendants that have deterministic constraints.
|
|
2159
|
+
* The array returned by this getter will only return the constraints that are marked as active. This array will contain
|
|
2160
|
+
* both constraints having this view as the source view and constraints having this view as the target view.
|
|
2161
|
+
*/
|
|
2162
|
+
get activeConstraints() { // <[BMLayoutConstraint]>
|
|
2163
|
+
let constraints = [];
|
|
2164
|
+
// If this view doesn't have deterministic constraints, skip them
|
|
2165
|
+
if (this._hasDeterministicConstraints) {
|
|
2166
|
+
constraints = this._constraints.filter(constraint => constraint.affectsLayout);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
this._subviews.forEach(subview => {
|
|
2170
|
+
constraints = constraints.concat(subview.activeConstraints);
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2173
|
+
return constraints;
|
|
2174
|
+
},
|
|
2175
|
+
|
|
2176
|
+
|
|
2177
|
+
/**
|
|
2178
|
+
* A list of layout constraints that have been registered for this view.
|
|
2179
|
+
*/
|
|
2180
|
+
get localConstraints() { // <[BMLayoutConstraint]>
|
|
2181
|
+
return this._constraints.slice();
|
|
2182
|
+
},
|
|
2183
|
+
|
|
2184
|
+
/**
|
|
2185
|
+
* Used internally.
|
|
2186
|
+
*/
|
|
2187
|
+
_invalidatedConstraints: NO, // <Boolean>
|
|
2188
|
+
|
|
2189
|
+
/**
|
|
2190
|
+
* Invoked internally when a constraint that affects this view is created.
|
|
2191
|
+
* @param constraint <BMLayoutConstraint> The constraint.
|
|
2192
|
+
*/
|
|
2193
|
+
_addConstraint(constraint) {
|
|
2194
|
+
this._constraints.push(constraint);
|
|
2195
|
+
this.rootView._invalidatedConstraints = YES;
|
|
2196
|
+
|
|
2197
|
+
this._checkConstraints();
|
|
2198
|
+
},
|
|
2199
|
+
|
|
2200
|
+
/**
|
|
2201
|
+
* Invoked internally to remove a constraint that is no longer needed.
|
|
2202
|
+
* @param constraint <BMLayoutConstraint> The constraint.
|
|
2203
|
+
*/
|
|
2204
|
+
_removeConstraint(constraint) {
|
|
2205
|
+
var index = this._constraints.indexOf(constraint);
|
|
2206
|
+
this.rootView._invalidatedConstraints = YES;
|
|
2207
|
+
|
|
2208
|
+
if (index != -1) {
|
|
2209
|
+
this._constraints.splice(index, 1);
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
this._checkConstraints();
|
|
2213
|
+
},
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* A property that controls if this view has deterministic constraints.
|
|
2217
|
+
*/
|
|
2218
|
+
_hasDeterministicConstraints: NO, // <Boolean>
|
|
2219
|
+
|
|
2220
|
+
/**
|
|
2221
|
+
* Invoked by CoreUI to determine if this view's size and positioning can be derived from its constraints.
|
|
2222
|
+
* A view has deterministic constraints if it has at least two horizontal constraints affecting different attributes,
|
|
2223
|
+
* and two vertical constraints affecting different attributes.
|
|
2224
|
+
*/
|
|
2225
|
+
_checkConstraints() {
|
|
2226
|
+
let hasDeterministicHorizontalConstraints = NO;
|
|
2227
|
+
let hasDeterministicVerticalConstraints = NO;
|
|
2228
|
+
|
|
2229
|
+
let horizontalAttributes = new Set;
|
|
2230
|
+
let verticalAttributes = new Set;
|
|
2231
|
+
|
|
2232
|
+
// A view has deterministic size if it has constant equality constraints for width and height
|
|
2233
|
+
// whose priority is strictly greater than the view's own compression and expansion resistance
|
|
2234
|
+
let hasDeterministicSize = NO;
|
|
2235
|
+
let hasDeterministicWidth = NO;
|
|
2236
|
+
let hasDeterministicHeight = NO;
|
|
2237
|
+
|
|
2238
|
+
// Width and height are implicit when this view has an intrinsic size.
|
|
2239
|
+
if (this.supportsIntrinsicSize) {
|
|
2240
|
+
horizontalAttributes.add(BMLayoutAttribute.Width);
|
|
2241
|
+
verticalAttributes.add(BMLayoutAttribute.Height);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// Then run through this view's constraints and add the attributes they affect into the appropriate sets
|
|
2245
|
+
let constraints = this._constraints;
|
|
2246
|
+
for (var i = 0; i < constraints.length; i++) {
|
|
2247
|
+
let constraint = constraints[i];
|
|
2248
|
+
|
|
2249
|
+
// Skip inactive constraints
|
|
2250
|
+
if (!constraint.affectsLayout) continue;
|
|
2251
|
+
|
|
2252
|
+
switch (constraint._kind) {
|
|
2253
|
+
case BMLayoutConstraintKind.Horizontal:
|
|
2254
|
+
constraint.affectedAttributesForView(this).forEach(attribute => horizontalAttributes.add(attribute));
|
|
2255
|
+
// If the set has two values or more, the axis is deterministic
|
|
2256
|
+
if (horizontalAttributes.size > 1) hasDeterministicHorizontalConstraints = YES;
|
|
2257
|
+
|
|
2258
|
+
if (constraint._sourceViewAttribute == BMLayoutAttribute.Width &&
|
|
2259
|
+
!constraint._targetView &&
|
|
2260
|
+
!constraint.isConstraintCollection &&
|
|
2261
|
+
constraint._relation == BMLayoutConstraintRelation.Equals &&
|
|
2262
|
+
constraint.activePriority > this._compressionResistance &&
|
|
2263
|
+
constraint.activePriority > this._expansionResistance) {
|
|
2264
|
+
hasDeterministicWidth = YES;
|
|
2265
|
+
}
|
|
2266
|
+
break;
|
|
2267
|
+
case BMLayoutConstraintKind.Vertical:
|
|
2268
|
+
constraint.affectedAttributesForView(this).forEach(attribute => verticalAttributes.add(attribute));
|
|
2269
|
+
// If the set has two values or more, the axis is deterministic
|
|
2270
|
+
if (verticalAttributes.size > 1) hasDeterministicVerticalConstraints = YES;
|
|
2271
|
+
|
|
2272
|
+
if (constraint._sourceViewAttribute == BMLayoutAttribute.Height &&
|
|
2273
|
+
!constraint._targetView &&
|
|
2274
|
+
!constraint.isConstraintCollection &&
|
|
2275
|
+
constraint._relation == BMLayoutConstraintRelation.Equals &&
|
|
2276
|
+
constraint.activePriority > this._compressionResistance &&
|
|
2277
|
+
constraint.activePriority > this._expansionResistance) {
|
|
2278
|
+
hasDeterministicHeight = YES;
|
|
2279
|
+
}
|
|
2280
|
+
break;
|
|
2281
|
+
default:
|
|
2282
|
+
break;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
if (hasDeterministicHeight && hasDeterministicWidth) hasDeterministicSize = YES;
|
|
2286
|
+
|
|
2287
|
+
// If both axes and the size are already determistic, stop looking
|
|
2288
|
+
if (hasDeterministicHorizontalConstraints && hasDeterministicVerticalConstraints && hasDeterministicSize) break;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
if (hasDeterministicSize) this._hasDeterministicSize = YES;
|
|
2292
|
+
|
|
2293
|
+
// If the view's layout is deterministic, cache the result
|
|
2294
|
+
if (hasDeterministicHorizontalConstraints && hasDeterministicVerticalConstraints) return this._hasDeterministicConstraints = YES;
|
|
2295
|
+
|
|
2296
|
+
// If the external constraints are not deterministic, also check the internal constraints, using the same logic as above
|
|
2297
|
+
constraints = this.internalConstraints();
|
|
2298
|
+
for (var i = 0; i < constraints.length; i++) {
|
|
2299
|
+
let constraint = constraints[i];
|
|
2300
|
+
|
|
2301
|
+
switch (constraint._kind) {
|
|
2302
|
+
case BMLayoutConstraintKind.Horizontal:
|
|
2303
|
+
constraint.affectedAttributesForView(this).forEach(attribute => horizontalAttributes.add(attribute));
|
|
2304
|
+
// If the set has two values or more, the axis is deterministic
|
|
2305
|
+
if (horizontalAttributes.size > 1) hasDeterministicHorizontalConstraints = YES;
|
|
2306
|
+
break;
|
|
2307
|
+
case BMLayoutConstraintKind.Vertical:
|
|
2308
|
+
constraint.affectedAttributesForView(this).forEach(attribute => verticalAttributes.add(attribute));
|
|
2309
|
+
// If the set has two values or more, the axis is deterministic
|
|
2310
|
+
if (verticalAttributes.size > 1) hasDeterministicVerticalConstraints = YES;
|
|
2311
|
+
break;
|
|
2312
|
+
default:
|
|
2313
|
+
break;
|
|
2314
|
+
}
|
|
2315
|
+
// If both axes are already determistic, stop looking
|
|
2316
|
+
if (hasDeterministicHorizontalConstraints && hasDeterministicVerticalConstraints) break;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// Finally cache the result into the `_hasDeterministicConstraints` property.
|
|
2320
|
+
this._hasDeterministicConstraints = hasDeterministicHorizontalConstraints && hasDeterministicVerticalConstraints;
|
|
2321
|
+
},
|
|
2322
|
+
|
|
2323
|
+
/**
|
|
2324
|
+
* Invoked internally to remove all the constraints from this view and all of its subviews.
|
|
2325
|
+
*/
|
|
2326
|
+
_clearConstraints() {
|
|
2327
|
+
// localConstraints operates on a copy of the constraints array, therefore the structure of the array
|
|
2328
|
+
// does not change as constraints are removed
|
|
2329
|
+
this.localConstraints.forEach(constraint => constraint.remove());
|
|
2330
|
+
this._subviews.forEach(subview => subview._clearConstraints());
|
|
2331
|
+
},
|
|
2332
|
+
|
|
2333
|
+
/**
|
|
2334
|
+
* Returns the Cassowary expression corresponding to this view's left variable.
|
|
2335
|
+
* This expressed as the sum between this view's left variable and its superview's left expression
|
|
2336
|
+
* and represents the horizontal distance from the rootView's origin point.
|
|
2337
|
+
* This makes it possible to create constraints that affect two views that are not direct descendants of
|
|
2338
|
+
* the same view but share at least an ancestor within their hierarchy.
|
|
2339
|
+
* @param multiplier <Number, nullable> Defaults to 1. An optional multiplier to apply to the final
|
|
2340
|
+
* term of this expression.
|
|
2341
|
+
*/
|
|
2342
|
+
_leftExpressionWithMultiplier(multiplier) { // <kiwi.Expression>
|
|
2343
|
+
multiplier = (multiplier || 1);
|
|
2344
|
+
if (this.isRootView) {
|
|
2345
|
+
return new kiwi.Expression([multiplier, this._variables[BMLayoutAttribute.Left]]);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
return new kiwi.Expression(this.superview._leftExpressionWithMultiplier(), [multiplier, this._variables[BMLayoutAttribute.Left]]);
|
|
2349
|
+
},
|
|
2350
|
+
|
|
2351
|
+
/**
|
|
2352
|
+
* Returns the Cassowary expression corresponding to this view's top variable.
|
|
2353
|
+
* This expressed as the sum between this view's top variable and its superview's top expression
|
|
2354
|
+
* and represents the vertical distance from the rootView's origin point.
|
|
2355
|
+
* This makes it possible to create constraints that affect two views that are not direct descendants of
|
|
2356
|
+
* the same view but share at least an ancestor within their hierarchy.
|
|
2357
|
+
* @param multiplier <Number, nullable> Defaults to 1. An optional multiplier to apply to the final
|
|
2358
|
+
* term of this expression.
|
|
2359
|
+
*/
|
|
2360
|
+
_topExpressionWithMultiplier(multiplier) { // <kiwi.Expression>
|
|
2361
|
+
multiplier = (multiplier || 1);
|
|
2362
|
+
if (this.isRootView) {
|
|
2363
|
+
return new kiwi.Expression([multiplier, this._variables[BMLayoutAttribute.Top]]);
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
return new kiwi.Expression(this.superview._topExpressionWithMultiplier(), [multiplier, this._variables[BMLayoutAttribute.Top]]);
|
|
2367
|
+
},
|
|
2368
|
+
|
|
2369
|
+
/**
|
|
2370
|
+
* Invoked internally to obtain the Cassowary expression corresponding to the given layout attribute.
|
|
2371
|
+
* The Left, Width, Top and Height are simple variables, but all other layout attributes are actually
|
|
2372
|
+
* treated as expressions between those four variables.
|
|
2373
|
+
*
|
|
2374
|
+
* Additionally, the Left and Top variables returned by this method are expressed in terms of the layout's
|
|
2375
|
+
* `rootView` coordinate space, unless otherwise requested, whereas locally they are epxressed in terms of the superview's coordinate space.
|
|
2376
|
+
*
|
|
2377
|
+
* Optionally, the attribute may be multiplied by the given multiplier.
|
|
2378
|
+
* @param attribute <BMLayoutAttribute> The layout attribute.
|
|
2379
|
+
* {
|
|
2380
|
+
* @param withMultiplier <Number, nullable> Defaults to <code>1</code>. An arbitrary multiplier for the attribute.
|
|
2381
|
+
* If this attribute is expressed as an expression, it will be
|
|
2382
|
+
* added to each of its terms. This value should not be <code>0</code>
|
|
2383
|
+
* }
|
|
2384
|
+
* @return <kiwi.Expression> The Cassowary expression for the variable.
|
|
2385
|
+
*/
|
|
2386
|
+
_cassowaryExpressionForLayoutAttribute(attribute, args) {
|
|
2387
|
+
var multiplier = (args && args.withMultiplier) || 1;
|
|
2388
|
+
|
|
2389
|
+
if (attribute in this._variables) {
|
|
2390
|
+
if (attribute == BMLayoutAttribute.Left) {
|
|
2391
|
+
return this._leftExpressionWithMultiplier(multiplier);
|
|
2392
|
+
}
|
|
2393
|
+
else if (attribute == BMLayoutAttribute.Top) {
|
|
2394
|
+
return this._topExpressionWithMultiplier(multiplier);
|
|
2395
|
+
}
|
|
2396
|
+
return new kiwi.Expression(multiplier != 1 ? [multiplier, this._variables[attribute]] : this._variables[attribute]);
|
|
2397
|
+
}
|
|
2398
|
+
else switch (attribute) {
|
|
2399
|
+
// The leading attribute is identical to Left for LTR layouts and to Right for RTL layouts
|
|
2400
|
+
case BMLayoutAttribute.Leading:
|
|
2401
|
+
return this.LTRLayout ?
|
|
2402
|
+
this._cassowaryExpressionForLayoutAttribute(BMLayoutAttribute.Left, args) :
|
|
2403
|
+
this._cassowaryExpressionForLayoutAttribute(BMLayoutAttribute.Right, args)
|
|
2404
|
+
|
|
2405
|
+
// The trailing attribute is identical to Right for LTR layouts and to Left for RTL layouts
|
|
2406
|
+
case BMLayoutAttribute.Trailing:
|
|
2407
|
+
return this.LTRLayout ?
|
|
2408
|
+
this._cassowaryExpressionForLayoutAttribute(BMLayoutAttribute.Right, args) :
|
|
2409
|
+
this._cassowaryExpressionForLayoutAttribute(BMLayoutAttribute.Left, args)
|
|
2410
|
+
|
|
2411
|
+
// Right layout attribute is expressed as Width + Left
|
|
2412
|
+
case BMLayoutAttribute.Right:
|
|
2413
|
+
return multiplier != 1 ?
|
|
2414
|
+
new kiwi.Expression(
|
|
2415
|
+
[multiplier, this._variables[BMLayoutAttribute.Width]],
|
|
2416
|
+
this._leftExpressionWithMultiplier(multiplier)
|
|
2417
|
+
) : new kiwi.Expression(
|
|
2418
|
+
this._variables[BMLayoutAttribute.Width],
|
|
2419
|
+
this._leftExpressionWithMultiplier(multiplier)
|
|
2420
|
+
);
|
|
2421
|
+
|
|
2422
|
+
// Center X layout attribute is expressed as Width/2 + Left
|
|
2423
|
+
case BMLayoutAttribute.CenterX:
|
|
2424
|
+
return multiplier != 1 ?
|
|
2425
|
+
new kiwi.Expression(
|
|
2426
|
+
[multiplier / 2, this._variables[BMLayoutAttribute.Width]],
|
|
2427
|
+
this._leftExpressionWithMultiplier(multiplier)
|
|
2428
|
+
) : new kiwi.Expression(
|
|
2429
|
+
[.5, this._variables[BMLayoutAttribute.Width]],
|
|
2430
|
+
this._leftExpressionWithMultiplier(multiplier)
|
|
2431
|
+
);
|
|
2432
|
+
|
|
2433
|
+
// Bottom layout attribute is expressed as Height + Top
|
|
2434
|
+
case BMLayoutAttribute.Bottom:
|
|
2435
|
+
return multiplier != 1 ?
|
|
2436
|
+
new kiwi.Expression(
|
|
2437
|
+
[multiplier, this._variables[BMLayoutAttribute.Height]],
|
|
2438
|
+
this._topExpressionWithMultiplier(multiplier)
|
|
2439
|
+
) : new kiwi.Expression(
|
|
2440
|
+
this._variables[BMLayoutAttribute.Height],
|
|
2441
|
+
this._topExpressionWithMultiplier(multiplier)
|
|
2442
|
+
);
|
|
2443
|
+
|
|
2444
|
+
// Center Y layout attribute is expressed as Height/2 + Top
|
|
2445
|
+
case BMLayoutAttribute.CenterY:
|
|
2446
|
+
return multiplier != 1 ?
|
|
2447
|
+
new kiwi.Expression(
|
|
2448
|
+
[multiplier / 2, this._variables[BMLayoutAttribute.Height]],
|
|
2449
|
+
this._topExpressionWithMultiplier(multiplier)
|
|
2450
|
+
) : new kiwi.Expression(
|
|
2451
|
+
[.5, this._variables[BMLayoutAttribute.Height]],
|
|
2452
|
+
this._topExpressionWithMultiplier(multiplier)
|
|
2453
|
+
);
|
|
2454
|
+
|
|
2455
|
+
// Aspect ratio attributes should not be retrieved through this method
|
|
2456
|
+
case BMLayoutAttribute.AspectRatio:
|
|
2457
|
+
throw new Error('Aspect ratio constraints are currently unsupported!');
|
|
2458
|
+
}
|
|
2459
|
+
},
|
|
2460
|
+
|
|
2461
|
+
|
|
2462
|
+
/**
|
|
2463
|
+
* Invoked by CoreUI prior to a layout pass to obtain internal constraints required by this view or is subviews,
|
|
2464
|
+
* as well as the constraints representing the intrinsic size for these views, if available.
|
|
2465
|
+
* The default implementation returns an empty array for a view without an intrinsic size and
|
|
2466
|
+
* various constraints for views that declare an intrinsic size.
|
|
2467
|
+
*
|
|
2468
|
+
* This will also return the internal constraints provided by this view and its subviews. Subclasses that need to
|
|
2469
|
+
* provide internal constraints should not override this getter. The `internalConstraints()` method serves as
|
|
2470
|
+
* an extension point for subclasses to override and include their own internal constraints.
|
|
2471
|
+
*/
|
|
2472
|
+
get builtInConstraints() { // <[BMLayoutConstraint]>
|
|
2473
|
+
var intrinsicSize = this._getIntrinsicSize();
|
|
2474
|
+
|
|
2475
|
+
var constraints = [];
|
|
2476
|
+
|
|
2477
|
+
if (intrinsicSize || this.supportsAutomaticIntrinsicSize) {
|
|
2478
|
+
if (this._widthCompressionConstraint) {
|
|
2479
|
+
this._widthCompressionConstraint.constant = intrinsicSize.width;
|
|
2480
|
+
this._heightCompressionConstraint.constant = intrinsicSize.height;
|
|
2481
|
+
constraints.push(this._widthCompressionConstraint, this._heightCompressionConstraint);
|
|
2482
|
+
}
|
|
2483
|
+
else {
|
|
2484
|
+
// If an intrinsic size is defined, create constraints to implement the compression resistance and expansion resistance
|
|
2485
|
+
constraints.push(
|
|
2486
|
+
this._widthCompressionConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Width, relatedBy: BMLayoutConstraintRelation.GreaterThanOrEquals, constant: intrinsicSize.width, priority: this.compressionResistance}),
|
|
2487
|
+
this._heightCompressionConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Height, relatedBy: BMLayoutConstraintRelation.GreaterThanOrEquals, constant: intrinsicSize.height, priority: this.compressionResistance})
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
if (this._widthExpansionConstraint) {
|
|
2492
|
+
this._widthExpansionConstraint.constant = intrinsicSize.width;
|
|
2493
|
+
this._heightExpansionConstraint.constant = intrinsicSize.height;
|
|
2494
|
+
constraints.push(this._widthExpansionConstraint, this._heightExpansionConstraint);
|
|
2495
|
+
}
|
|
2496
|
+
else {
|
|
2497
|
+
// If an intrinsic size is defined, create constraints to implement the compression resistance and expansion resistance
|
|
2498
|
+
constraints.push(
|
|
2499
|
+
this._widthExpansionConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Width, relatedBy: BMLayoutConstraintRelation.LessThanOrEquals, constant: intrinsicSize.width, priority: this.expansionResistance}),
|
|
2500
|
+
this._heightExpansionConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Height, relatedBy: BMLayoutConstraintRelation.LessThanOrEquals, constant: intrinsicSize.height, priority: this.expansionResistance})
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
else {
|
|
2505
|
+
// TODO: Moved to _resizeForAutomaticIntrinsicSize
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// Add this view's own internal constraints
|
|
2509
|
+
constraints = constraints.concat(this.internalConstraints());
|
|
2510
|
+
|
|
2511
|
+
this._internalConstraints = constraints;
|
|
2512
|
+
|
|
2513
|
+
// Append the internal constraints for subviews
|
|
2514
|
+
this._subviews.forEach(subview => {
|
|
2515
|
+
constraints = constraints.concat(subview.builtInConstraints);
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2518
|
+
return constraints;
|
|
2519
|
+
},
|
|
2520
|
+
|
|
2521
|
+
/**
|
|
2522
|
+
* Invoked by CoreUI prior to a layout pass to obtain internal constraints required by this view.
|
|
2523
|
+
* The default implementation returns an empty array for all views except the layout root view.
|
|
2524
|
+
*
|
|
2525
|
+
* Subclasses overriding this method should invoke the superclass implementation and include
|
|
2526
|
+
* any constraints returned by it into their result. Views are expected to return the same set of constraints from
|
|
2527
|
+
* this method unless they invalidate their constraint set. During all other layout passes, views should just
|
|
2528
|
+
* modify and return their previously returned set of constraints.
|
|
2529
|
+
*
|
|
2530
|
+
* Constraints created from within this method must be marked internal.
|
|
2531
|
+
* @return <[BMLayoutConstraint]> An array of layout constraints needed by this view.
|
|
2532
|
+
*/
|
|
2533
|
+
internalConstraints() {
|
|
2534
|
+
var constraints = [];
|
|
2535
|
+
|
|
2536
|
+
// If this view is the root of the view hierarchy, add the constraints fixing it to the top-left corner of its container
|
|
2537
|
+
// and sizing constraints making it as large as its container
|
|
2538
|
+
if (this.isRootView) {
|
|
2539
|
+
// Root constraints cannot be created if the view's node is not attached to the document
|
|
2540
|
+
if (!this.node.parentNode) return constraints;
|
|
2541
|
+
|
|
2542
|
+
if (this._rootViewLeftConstraint) {
|
|
2543
|
+
if (this._layoutEditor) {
|
|
2544
|
+
this._rootViewHeightConstraint.constant = this._layoutEditor._staticWorkspaceHeight;
|
|
2545
|
+
this._rootViewWidthConstraint.constant = this._layoutEditor._staticWorkspaceWidth;
|
|
2546
|
+
}
|
|
2547
|
+
else {
|
|
2548
|
+
this._rootViewHeightConstraint.constant = this._node.parentNode.offsetHeight;
|
|
2549
|
+
this._rootViewWidthConstraint.constant = this._node.parentNode.offsetWidth;
|
|
2550
|
+
}
|
|
2551
|
+
constraints.push(this._rootViewLeftConstraint, this._rootViewTopConstraint, this._rootViewWidthConstraint, this._rootViewHeightConstraint);
|
|
2552
|
+
}
|
|
2553
|
+
else {
|
|
2554
|
+
constraints.push(
|
|
2555
|
+
this._rootViewLeftConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Left, relatedBy: BMLayoutConstraintRelation.Equals, constant: 0}),
|
|
2556
|
+
this._rootViewTopConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Top, relatedBy: BMLayoutConstraintRelation.Equals, constant: 0}),
|
|
2557
|
+
this._rootViewWidthConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Width, relatedBy: BMLayoutConstraintRelation.Equals, constant: this._node.parentNode.offsetWidth}),
|
|
2558
|
+
this._rootViewHeightConstraint = BMLayoutConstraint.internalConstraintWithView(this, {attribute: BMLayoutAttribute.Height, relatedBy: BMLayoutConstraintRelation.Equals, constant: this._node.parentNode.offsetHeight})
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
return constraints;
|
|
2564
|
+
|
|
2565
|
+
},
|
|
2566
|
+
|
|
2567
|
+
/**
|
|
2568
|
+
* Invoked internally by CoreUI to assign the layout width to this view.
|
|
2569
|
+
*/
|
|
2570
|
+
_assignWidth() {
|
|
2571
|
+
this._requiredWidth = this._variables[BMLayoutAttribute.Width].value();
|
|
2572
|
+
|
|
2573
|
+
// If the preferred intrinsic size is different from the assigned intrinsic size, mark this view as requiring an intrinsic size measurement
|
|
2574
|
+
if (!this._hasDeterministicSize && this._intrinsicSize && this._preferredIntrinsicSize && !this._instrinsicSize.isEqualToSize(this._preferredIntrinsicSize) && this._intrinsicSize.width != this._requiredWidth) {
|
|
2575
|
+
this._needsIntrinsicSizeMeasurement = YES;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
this._subviews.forEach(subview => subview._assignWidth());
|
|
2579
|
+
},
|
|
2580
|
+
|
|
2581
|
+
/**
|
|
2582
|
+
* Invoked by CoreUI prior to measuring intrinsic sizes to give the views a chance to acquire their reference frames.
|
|
2583
|
+
* The reference is computed based on the node's position and size within the DOM.
|
|
2584
|
+
*
|
|
2585
|
+
* If this view does not have deterministic constraints or its layout cannot be computed by CoreUI, this reference frame will
|
|
2586
|
+
* be assigned back to this view at the end of the layout operation.
|
|
2587
|
+
*/
|
|
2588
|
+
_acquireReferenceFrames() {
|
|
2589
|
+
// If this view has already had a frame applied, the reference frame no longer needs to be measured, which should avoid triggering any forced layout
|
|
2590
|
+
if (this._frame) {
|
|
2591
|
+
this._referenceFrame = this._frame.copy();
|
|
2592
|
+
}
|
|
2593
|
+
else {
|
|
2594
|
+
let referenceFrame = BMRectMake(this.node.offsetLeft, this.node.offsetTop, this.node.offsetWidth, this.node.offsetHeight);
|
|
2595
|
+
|
|
2596
|
+
// Only set this reference frame if the view doesn't already have a reference frame
|
|
2597
|
+
// and if this reference frame has non-zero sizes
|
|
2598
|
+
if ((referenceFrame.width && referenceFrame.height) || !this._referenceFrame) {
|
|
2599
|
+
this._referenceFrame = referenceFrame;
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
//if (this._intrinsicSize && this._preferredIntrinsicSize && !this._instrinsicSize.isEqualToSize(this._preferredIntrinsicSize)) {
|
|
2604
|
+
// Invalidate the intrinsic size for this view if its assigned size is less than its preferred size
|
|
2605
|
+
// and the view does not have a deterministic size
|
|
2606
|
+
if (!this._hasDeterministicSize && this._intrinsicSize && this._preferredIntrinsicSize && !this._instrinsicSize.isGreaterThanSize(this._preferredIntrinsicSize)) {
|
|
2607
|
+
this._needsIntrinsicSizeMeasurement = YES;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
this._subviews.forEach(subview => subview._acquireReferenceFrames());
|
|
2611
|
+
},
|
|
2612
|
+
|
|
2613
|
+
/**
|
|
2614
|
+
* Invoked by CoreUI to resize this view's node for automatic intrinsic size measurement.
|
|
2615
|
+
*/
|
|
2616
|
+
_resizeForAutomaticInstricSize() {
|
|
2617
|
+
|
|
2618
|
+
// If the subview supports automatic intrinsic size
|
|
2619
|
+
// make its maximum size infinite prior to requesting the intrinsic size
|
|
2620
|
+
// This is done in its own loop to prevent excessive thrashing
|
|
2621
|
+
if (this.supportsAutomaticIntrinsicSize && this.needsIntrinsicSizeMeasurement) {
|
|
2622
|
+
if (this._requiredWidth) {
|
|
2623
|
+
this._node.style.width = this._requiredWidth + 'px';
|
|
2624
|
+
}
|
|
2625
|
+
else {
|
|
2626
|
+
BMCopyProperties(this._node.style, {width: 'auto', height: 'auto', left: '0px', top: '0px', position: 'absolute'});
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
else {
|
|
2630
|
+
// TODO: Moved from builtInConstraints
|
|
2631
|
+
|
|
2632
|
+
// If an intrinsic size is not specified or automatically determined, set this view to maximum width & height
|
|
2633
|
+
// temporarily to allow its subviews to measure their own intrinsic sizes correctly
|
|
2634
|
+
// but only if there are subviews to measure
|
|
2635
|
+
if (this.needsIntrinsicSizeMeasurement) {
|
|
2636
|
+
BMCopyProperties(this._node.style, {width: '100%', height: '100%'});
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
this._subviews.forEach(subview => subview._resizeForAutomaticInstricSize());
|
|
2641
|
+
},
|
|
2642
|
+
|
|
2643
|
+
/**
|
|
2644
|
+
* Invoked internally by CoreUI to prepare the view's node to be measured by CoreUI, if it supports
|
|
2645
|
+
* automatic intrinsic sizes.
|
|
2646
|
+
*/
|
|
2647
|
+
_prepareForAutomaticIntrinsicSize() {
|
|
2648
|
+
// Save the reference frame before attempting to modify this view's attributes
|
|
2649
|
+
if (this._requiredWidth === undefined) {
|
|
2650
|
+
this._acquireReferenceFrames();
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
this._resizeForAutomaticInstricSize();
|
|
2654
|
+
},
|
|
2655
|
+
|
|
2656
|
+
/**
|
|
2657
|
+
* Invoked internally by CoreUI during the second phase of the layout process, after assigning
|
|
2658
|
+
* a width to each view.
|
|
2659
|
+
* Updates the internal height constraints of this view and all of its subviews to match the new intrinsic size.
|
|
2660
|
+
*/
|
|
2661
|
+
_updateInternalHeightConstraints() {
|
|
2662
|
+
var size = this._getIntrinsicSize();
|
|
2663
|
+
|
|
2664
|
+
if (size && this._heightCompressionConstraint) {
|
|
2665
|
+
this._heightCompressionConstraint.constant = size.height;
|
|
2666
|
+
this._heightExpansionConstraint.constant = size.height;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
this._subviews.forEach(subview => subview._updateInternalHeightConstraints());
|
|
2670
|
+
},
|
|
2671
|
+
|
|
2672
|
+
/**
|
|
2673
|
+
* Returns all the horizontal layout variables used by this view hierarchy.
|
|
2674
|
+
*/
|
|
2675
|
+
get _horizontalLayoutVariables() { // <[kiwi.Variable]>
|
|
2676
|
+
var variables = [this._variables[BMLayoutAttribute.Left], this._variables[BMLayoutAttribute.Width]];
|
|
2677
|
+
|
|
2678
|
+
this._subviews.forEach(subview => variables = variables.concat(subview._horizontalLayoutVariables));
|
|
2679
|
+
|
|
2680
|
+
return variables;
|
|
2681
|
+
},
|
|
2682
|
+
|
|
2683
|
+
|
|
2684
|
+
/**
|
|
2685
|
+
* Returns all the vertical layout variables used by this view hierarchy.
|
|
2686
|
+
*/
|
|
2687
|
+
get _verticalLayoutVariables() { // <[kiwi.Variable]>
|
|
2688
|
+
var variables = [this._variables[BMLayoutAttribute.Top], this._variables[BMLayoutAttribute.Height]];
|
|
2689
|
+
|
|
2690
|
+
this._subviews.forEach(subview => variables = variables.concat(subview._verticalLayoutVariables));
|
|
2691
|
+
|
|
2692
|
+
return variables;
|
|
2693
|
+
},
|
|
2694
|
+
|
|
2695
|
+
// #endregion
|
|
2696
|
+
|
|
2697
|
+
// #region Layout pass
|
|
2698
|
+
|
|
2699
|
+
/**
|
|
2700
|
+
* Used internally.
|
|
2701
|
+
*/
|
|
2702
|
+
_isPerformingLayoutPass: NO, // <Boolean>
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* Used internally.
|
|
2706
|
+
*/
|
|
2707
|
+
_layoutAnimationFrameIdentifier: undefined, // <Number>
|
|
2708
|
+
|
|
2709
|
+
/**
|
|
2710
|
+
* Cancels any pending layout passes and removes this view from its layout queue.
|
|
2711
|
+
*/
|
|
2712
|
+
_cancelLayout() {
|
|
2713
|
+
this._layoutQueue._removeView(this);
|
|
2714
|
+
if (this._layoutAnimationFrameIdentifier) {
|
|
2715
|
+
window.cancelAnimationFrame(this._layoutAnimationFrameIdentifier);
|
|
2716
|
+
}
|
|
2717
|
+
this._layoutAnimationFrameIdentifier = undefined;
|
|
2718
|
+
this._layoutIntrisicSizeInvalidationIdentifier = undefined;
|
|
2719
|
+
},
|
|
2720
|
+
|
|
2721
|
+
/**
|
|
2722
|
+
* Enqueues this view in its layout queue.
|
|
2723
|
+
*/
|
|
2724
|
+
_registerLayout() {
|
|
2725
|
+
this._layoutQueue._enqueueView(this);
|
|
2726
|
+
},
|
|
2727
|
+
|
|
2728
|
+
/**
|
|
2729
|
+
* Schedules a layout pass before the next animation frame.
|
|
2730
|
+
*/
|
|
2731
|
+
async _scheduleLayout() {
|
|
2732
|
+
if (this != this.rootView) return this.rootView._scheduleLayout();
|
|
2733
|
+
if (this._layoutAnimator) {
|
|
2734
|
+
await this._layoutAnimator;
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// If an immediate or regular layout pass was already pending, do nothing
|
|
2738
|
+
if (this._layoutAnimationFrameIdentifier || this._layoutIntrisicSizeInvalidationIdentifier) return;
|
|
2739
|
+
|
|
2740
|
+
this._registerLayout();
|
|
2741
|
+
//_BMViewLayoutQueue.add(this);
|
|
2742
|
+
this._layoutAnimationFrameIdentifier = window.requestAnimationFrame(_ => {
|
|
2743
|
+
this._layoutAnimationFrameIdentifier = undefined;
|
|
2744
|
+
this._layoutQueue.dequeue();
|
|
2745
|
+
//_BMViewDequeueLayoutQueue();
|
|
2746
|
+
//this.layoutSubviews();
|
|
2747
|
+
});
|
|
2748
|
+
},
|
|
2749
|
+
|
|
2750
|
+
/**
|
|
2751
|
+
* Used internally.
|
|
2752
|
+
*/
|
|
2753
|
+
_layoutIntrisicSizeInvalidationIdentifier: undefined, // <Number>
|
|
2754
|
+
|
|
2755
|
+
/**
|
|
2756
|
+
* Schedules a layout pass in response to an intrinsic size invalidation by this view or one of its subviews.
|
|
2757
|
+
*
|
|
2758
|
+
* Unlike the regular layout invalidation performed by `_scheduleLayout`, CoreUI will attempt to process this
|
|
2759
|
+
* request before the next animation frame has a chance to render in order to avoid potential visual artifacts
|
|
2760
|
+
* that may occur when views' content change independent of their frames.
|
|
2761
|
+
*/
|
|
2762
|
+
async _scheduleImmediateLayout() {
|
|
2763
|
+
if (this != this.rootView) return this.rootView._scheduleImmediateLayout();
|
|
2764
|
+
|
|
2765
|
+
if (this._layoutAnimator) {
|
|
2766
|
+
await this._layoutAnimator;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
// If a regular layout pass was pending, cancel it as the immediate layout will run faster
|
|
2770
|
+
if (this._layoutAnimationFrameIdentifier) {
|
|
2771
|
+
window.cancelAnimationFrame(this._layoutAnimationFrameIdentifier);
|
|
2772
|
+
this._layoutAnimationFrameIdentifier = undefined;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// If an immediate layout pass is already pending, do nothing
|
|
2776
|
+
if (this._layoutIntrisicSizeInvalidationIdentifier) return;
|
|
2777
|
+
this._layoutIntrisicSizeInvalidationIdentifier = BMUUIDMake();
|
|
2778
|
+
var identifier = this._layoutIntrisicSizeInvalidationIdentifier;
|
|
2779
|
+
|
|
2780
|
+
this._registerLayout();
|
|
2781
|
+
|
|
2782
|
+
// Await 0 will move this into the next event tick and should be faster than `window.postMessage` or `window.setTimeout`
|
|
2783
|
+
await 0;
|
|
2784
|
+
if (this._layoutIntrisicSizeInvalidationIdentifier && identifier == this._layoutIntrisicSizeInvalidationIdentifier) {
|
|
2785
|
+
this._layoutIntrisicSizeInvalidationIdentifier = undefined;
|
|
2786
|
+
this._layoutQueue.dequeue();
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
return;
|
|
2790
|
+
/*
|
|
2791
|
+
//_BMViewLayoutQueue.add(this);
|
|
2792
|
+
|
|
2793
|
+
// A message is posted to this window to handle the invalidation immediately
|
|
2794
|
+
// The message is added to the end of the current event queue, so it performs faster than
|
|
2795
|
+
// setTimeout and similar to setImmediate
|
|
2796
|
+
let self = this;
|
|
2797
|
+
function handler(event) {
|
|
2798
|
+
// Don't handle messages that come from different view hierarchies
|
|
2799
|
+
if (event.data != self._layoutIntrisicSizeInvalidationIdentifier) return;
|
|
2800
|
+
|
|
2801
|
+
self._layoutIntrisicSizeInvalidationIdentifier = undefined;
|
|
2802
|
+
self._layoutQueue.dequeue();
|
|
2803
|
+
//_BMViewDequeueLayoutQueue();
|
|
2804
|
+
//self.layoutSubviews();
|
|
2805
|
+
|
|
2806
|
+
// Once the request is honored, the event handler is removed
|
|
2807
|
+
window.removeEventListener('message', handler);
|
|
2808
|
+
this._layoutIntrisicSizeInvalidationHandler = undefined;
|
|
2809
|
+
}
|
|
2810
|
+
this._layoutIntrisicSizeInvalidationHandler = handler;
|
|
2811
|
+
|
|
2812
|
+
window.addEventListener('message', handler);
|
|
2813
|
+
window.postMessage(this._layoutIntrisicSizeInvalidationIdentifier, '*');
|
|
2814
|
+
*/
|
|
2815
|
+
},
|
|
2816
|
+
|
|
2817
|
+
layout: function() {
|
|
2818
|
+
return this.layoutIfNeeded();
|
|
2819
|
+
},
|
|
2820
|
+
|
|
2821
|
+
/**
|
|
2822
|
+
* Should be invoked to perform an immediate layout pass on the view hierarchy to
|
|
2823
|
+
* which this view belongs, if this hierarchy's layout had been invalidated.
|
|
2824
|
+
*
|
|
2825
|
+
* If this view's layout has not been invalidated, this method does nothing.
|
|
2826
|
+
*/
|
|
2827
|
+
layoutIfNeeded: async function() {
|
|
2828
|
+
// Only the root view will handle the layout
|
|
2829
|
+
if (this != this.rootView) return this.rootView.layout();
|
|
2830
|
+
|
|
2831
|
+
if (!this._needsLayout) return;
|
|
2832
|
+
|
|
2833
|
+
if (this._layoutAnimator) {
|
|
2834
|
+
await this._layoutAnimator;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
this._registerLayout();
|
|
2838
|
+
this._layoutQueue.dequeue();
|
|
2839
|
+
//_BMViewLayoutQueue.add(this);
|
|
2840
|
+
//_BMViewDequeueLayoutQueue();
|
|
2841
|
+
//this.layoutSubviews();
|
|
2842
|
+
},
|
|
2843
|
+
|
|
2844
|
+
/**
|
|
2845
|
+
* @deprecated - Use `layoutIfNeeded()`
|
|
2846
|
+
*
|
|
2847
|
+
* Should be invoked to perform an immediate layout pass on the view hierarchy to
|
|
2848
|
+
* which this view belongs.
|
|
2849
|
+
*/
|
|
2850
|
+
// layout: undefined, // <Object>
|
|
2851
|
+
|
|
2852
|
+
/**
|
|
2853
|
+
* @deprecated - Superseeded by `_layoutSubviewsGenerator`
|
|
2854
|
+
*
|
|
2855
|
+
* Invoked by the layout engine to cause this view to layout its subviews.
|
|
2856
|
+
* This method should not be invoked manually to cause a layout pass, instead set the
|
|
2857
|
+
* <code>needsLayout</code> property to <code>YES</code> which will schedule a layout pass on the next run loop.
|
|
2858
|
+
* If an immediate layout pass is required, the <code>layout()</code> method should be invoked instead.
|
|
2859
|
+
* CoreUI will then invoke this method as needed.
|
|
2860
|
+
*
|
|
2861
|
+
* The default implementation lays out subviews based on the constraints that have been added to the view hierarchy.
|
|
2862
|
+
*/
|
|
2863
|
+
layoutSubviews() {
|
|
2864
|
+
// When run synchronously, the entire generator is run in one step
|
|
2865
|
+
let generator = this._layoutSubviewsGenerator();
|
|
2866
|
+
|
|
2867
|
+
let done = NO;
|
|
2868
|
+
while (!done) {
|
|
2869
|
+
done = generator.next().done;
|
|
2870
|
+
}
|
|
2871
|
+
},
|
|
2872
|
+
|
|
2873
|
+
|
|
2874
|
+
/**
|
|
2875
|
+
* A generator that is used internally by CoreUI to layout subviews.
|
|
2876
|
+
*/
|
|
2877
|
+
*_layoutSubviewsGenerator() {
|
|
2878
|
+
|
|
2879
|
+
// Upon starting the layout pass, cancel all other pending layout passes
|
|
2880
|
+
// This is needed here because as of CoreUI 2.2 a layout pass request by one view hierarchy
|
|
2881
|
+
// can trigger a layout pass in a separate view hierarchy
|
|
2882
|
+
if (this._layoutAnimationFrameIdentifier) {
|
|
2883
|
+
window.cancelAnimationFrame(this._layoutAnimationFrameIdentifier);
|
|
2884
|
+
this._layoutAnimationFrameIdentifier = undefined;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
if (this._layoutIntrisicSizeInvalidationIdentifier) {
|
|
2888
|
+
window.removeEventListener('message', this._layoutIntrisicSizeInvalidationHandler);
|
|
2889
|
+
this._layoutIntrisicSizeInvalidationHandler = undefined;
|
|
2890
|
+
this._layoutIntrisicSizeInvalidationIdentifier = undefined;
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
// If this view has been released or detached from the DOM while it had a pending layout pass request
|
|
2894
|
+
// cancel this layout operation
|
|
2895
|
+
if (!this._node || !this._node.parentNode) return;
|
|
2896
|
+
|
|
2897
|
+
// Cancel this operation if this view has moved from the root of its hierarchy
|
|
2898
|
+
if (this.superview) return;
|
|
2899
|
+
|
|
2900
|
+
if (BMViewDebug) console.log(`
|
|
2901
|
+
***********************************************************************************************************
|
|
2902
|
+
[BMCoreUI] Layout pass began
|
|
2903
|
+
***********************************************************************************************************
|
|
2904
|
+
`);
|
|
2905
|
+
|
|
2906
|
+
// The layout process happens in two passes:
|
|
2907
|
+
|
|
2908
|
+
// For the first pass, CoreUI asks views for their intrinsic sizes then uses that information
|
|
2909
|
+
// to compute the horizontal position and size for them
|
|
2910
|
+
// After assigning those, the views are asked for their intrinsic sizes again and a second
|
|
2911
|
+
// layout computation begins to compute vertical positions and sizes
|
|
2912
|
+
var activeConstraints = new Set();
|
|
2913
|
+
|
|
2914
|
+
// If the size classes were invalidated, recompute them - this may potentially cause constraints to become invalidated
|
|
2915
|
+
if (this._invalidatedSizeClasses) {
|
|
2916
|
+
this._updateSizeClasses();
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
this._updateViewport();
|
|
2920
|
+
|
|
2921
|
+
// Create a solver for the horizontal equations
|
|
2922
|
+
// Solvers are re-used unless the constraints have changed or this view is in edit mode
|
|
2923
|
+
var solver;
|
|
2924
|
+
let needsUpdatingConstraints = NO;
|
|
2925
|
+
if (!this._solver || this._layoutEditor || this._invalidatedConstraints) {
|
|
2926
|
+
solver = new kiwi.Solver();
|
|
2927
|
+
this._solver = solver;
|
|
2928
|
+
needsUpdatingConstraints = YES;
|
|
2929
|
+
|
|
2930
|
+
// If the layout is currently being edited, discard all intrinsic sizes
|
|
2931
|
+
if (this._layoutEditor) {
|
|
2932
|
+
this.allSubviews.forEach(subview => subview._needsIntrinsicSizeMeasurement = YES);
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
this._invalidatedConstraints = NO;
|
|
2936
|
+
}
|
|
2937
|
+
else {
|
|
2938
|
+
solver = this._solver;
|
|
2939
|
+
needsUpdatingConstraints = NO;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
this._prepareForAutomaticIntrinsicSize();
|
|
2943
|
+
// this._subviews.forEach(subview => {
|
|
2944
|
+
// subview._prepareForAutomaticIntrinsicSize();
|
|
2945
|
+
// });
|
|
2946
|
+
|
|
2947
|
+
// Suspend execution at this point to allow other views to update the DOM
|
|
2948
|
+
yield;
|
|
2949
|
+
|
|
2950
|
+
// If a new solver has been created, initialize the active constraints and variables
|
|
2951
|
+
if (needsUpdatingConstraints) {
|
|
2952
|
+
this.activeConstraints.forEach(constraint => {
|
|
2953
|
+
activeConstraints.add(constraint);
|
|
2954
|
+
});
|
|
2955
|
+
|
|
2956
|
+
// Also the internal and active constraints, temporarily suspending automatically attaching
|
|
2957
|
+
// constraints to view for this
|
|
2958
|
+
this.builtInConstraints.forEach(constraint => {
|
|
2959
|
+
activeConstraints.add(constraint);
|
|
2960
|
+
});
|
|
2961
|
+
|
|
2962
|
+
// Add the horizontal variables to the solver
|
|
2963
|
+
this._horizontalLayoutVariables.forEach(variable => solver.addEditVariable(variable, kiwi.Strength.strong));
|
|
2964
|
+
|
|
2965
|
+
// If the solver fails, this variable will contain the constraints that must be ignored for the layout pass to succeed
|
|
2966
|
+
/** @type {Object[]} */let constraintsToIgnore;
|
|
2967
|
+
|
|
2968
|
+
// Feed it with the horizontal constraints
|
|
2969
|
+
while (YES) {
|
|
2970
|
+
// Create a list of attempted constraints to let the user know which constraints failed
|
|
2971
|
+
let lastConstraint;
|
|
2972
|
+
let attemptedConstraints = [];
|
|
2973
|
+
let attemptedCassowaryConstraints = [];
|
|
2974
|
+
try {
|
|
2975
|
+
activeConstraints.forEach(constraint => {
|
|
2976
|
+
// Skip constraints that have cause the equations to be unsolvable
|
|
2977
|
+
if (constraintsToIgnore && constraintsToIgnore.includes(constraint)) return;
|
|
2978
|
+
|
|
2979
|
+
// Add each constraint's complete set of constituent constraints to the solver
|
|
2980
|
+
if (constraint._kind === BMLayoutConstraintKind.Horizontal) {
|
|
2981
|
+
lastConstraint = constraint;
|
|
2982
|
+
attemptedConstraints.push(constraint);
|
|
2983
|
+
if (BMViewDebug) console.log(`[BMCoreUI] Horizontal constraint: ${constraint}`);
|
|
2984
|
+
constraint.constituentConstraints.forEach(constraint => {
|
|
2985
|
+
constraint._constraint = undefined;
|
|
2986
|
+
let cassowaryConstraint = constraint._cassowaryConstraint;
|
|
2987
|
+
attemptedCassowaryConstraints.push(cassowaryConstraint);
|
|
2988
|
+
solver.addConstraint(cassowaryConstraint);
|
|
2989
|
+
constraint._solver = solver;
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
});
|
|
2993
|
+
break;
|
|
2994
|
+
}
|
|
2995
|
+
catch (e) {
|
|
2996
|
+
|
|
2997
|
+
// If an error is thrown, attempt to re-do the layout by removing the offending constraint
|
|
2998
|
+
console.error(`[BMCoreUI] Unsatisfiable constraints. The following constraints are not satisfiable and the
|
|
2999
|
+
layout operation cannot be completed. Will attempt to solve the layout by breaking constraint
|
|
3000
|
+
${lastConstraint}. Please check the following constraints at design-time to resolve the problem:\n`);
|
|
3001
|
+
|
|
3002
|
+
attemptedConstraints.forEach(constraint => {
|
|
3003
|
+
console.error(`${constraint}`);
|
|
3004
|
+
});
|
|
3005
|
+
|
|
3006
|
+
// At design-time, the process should stop in its tracks
|
|
3007
|
+
if (this._layoutEditor) return;
|
|
3008
|
+
|
|
3009
|
+
constraintsToIgnore = constraintsToIgnore || [];
|
|
3010
|
+
constraintsToIgnore.push(lastConstraint);
|
|
3011
|
+
//lastConstraint.isActive = NO;
|
|
3012
|
+
|
|
3013
|
+
// Remove all previously attempted constraints and re-try
|
|
3014
|
+
attemptedCassowaryConstraints.forEach(constraint => {
|
|
3015
|
+
if (solver.hasConstraint(constraint)) solver.removeConstraint(constraint);
|
|
3016
|
+
});
|
|
3017
|
+
|
|
3018
|
+
// TODO - verify
|
|
3019
|
+
|
|
3020
|
+
//return this.layoutSubviews();
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
else {
|
|
3025
|
+
// Otherwise just update the intrinsic sizing constraints as needed
|
|
3026
|
+
this.builtInConstraints || console.log();
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// Suggest the origin point for the root view's position
|
|
3030
|
+
try {
|
|
3031
|
+
solver.suggestValue(this._variables[BMLayoutAttribute.Left], 0);
|
|
3032
|
+
}
|
|
3033
|
+
catch (e) {
|
|
3034
|
+
// If dual optimize fails, the intrinsic sizes lead to an unsolvable layout
|
|
3035
|
+
console.log(e);
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
let allSubviews = this.allSubviews;
|
|
3039
|
+
|
|
3040
|
+
// Suggest values for the intrinsic sizes
|
|
3041
|
+
allSubviews.forEach(subview => {
|
|
3042
|
+
if (subview._widthCompressionConstraint) try {
|
|
3043
|
+
solver.suggestValue(subview._variables[BMLayoutAttribute.Width], subview._widthCompressionConstraint._constant);
|
|
3044
|
+
}
|
|
3045
|
+
catch (e) {
|
|
3046
|
+
// If dual optimize fails, the intrinsic sizes lead to an unsolvable layout
|
|
3047
|
+
console.log(e);
|
|
3048
|
+
}
|
|
3049
|
+
});
|
|
3050
|
+
|
|
3051
|
+
// The solver is now prepared to update the variables
|
|
3052
|
+
solver.updateVariables();
|
|
3053
|
+
|
|
3054
|
+
// Suspend at this point to allow other views to read the updated DOM
|
|
3055
|
+
yield;
|
|
3056
|
+
|
|
3057
|
+
// When there are no subviews, assign the width and prepare the root view for automatic intrinsic size
|
|
3058
|
+
if (this.supportsAutomaticIntrinsicSize && !this._subviews.length) {
|
|
3059
|
+
this._assignWidth();
|
|
3060
|
+
this._prepareForAutomaticIntrinsicSize();
|
|
3061
|
+
this._updateInternalHeightConstraints();
|
|
3062
|
+
} else {
|
|
3063
|
+
this._subviews.forEach(subview => {
|
|
3064
|
+
// With the horizontal positions resolved, the assigned width should be set as required
|
|
3065
|
+
// for the subviews to update their intrinsic positions
|
|
3066
|
+
subview._assignWidth();
|
|
3067
|
+
// Because the width have been assigned, the reference frames will not be captured from within this method, therefore
|
|
3068
|
+
// layout thrashing should not occur when invoking this method separately on each subview
|
|
3069
|
+
subview._prepareForAutomaticIntrinsicSize();
|
|
3070
|
+
subview._updateInternalHeightConstraints();
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
// Suspend at this point to allow other views to update the DOM
|
|
3075
|
+
yield;
|
|
3076
|
+
|
|
3077
|
+
|
|
3078
|
+
if (needsUpdatingConstraints) {
|
|
3079
|
+
// Add the horizontal variables to the solver
|
|
3080
|
+
this._verticalLayoutVariables.forEach(variable => solver.addEditVariable(variable, kiwi.Strength.strong));
|
|
3081
|
+
|
|
3082
|
+
// If the solver fails, this variable will contain the constraints that must be ignored for the layout pass to succeed
|
|
3083
|
+
/** @type {Object[]} */let constraintsToIgnore;
|
|
3084
|
+
|
|
3085
|
+
// Feed new solver with the vertical constraints
|
|
3086
|
+
while (YES) {
|
|
3087
|
+
// Create a list of attempted constraints to let the user know which constraints failed
|
|
3088
|
+
let lastConstraint;
|
|
3089
|
+
let attemptedCassowaryConstraints = [];
|
|
3090
|
+
let attemptedConstraints = [];
|
|
3091
|
+
try {
|
|
3092
|
+
activeConstraints.forEach(constraint => {
|
|
3093
|
+
// Skip constraints that have cause the equations to be unsolvable
|
|
3094
|
+
if (constraintsToIgnore && constraintsToIgnore.includes(constraint)) return;
|
|
3095
|
+
|
|
3096
|
+
// Add each constraint's complete set of constituent constraints to the solver
|
|
3097
|
+
lastConstraint = constraint;
|
|
3098
|
+
attemptedConstraints.push(constraint);
|
|
3099
|
+
if (constraint._kind === BMLayoutConstraintKind.Vertical) {
|
|
3100
|
+
constraint.constituentConstraints.forEach(constraint => {
|
|
3101
|
+
constraint._constraint = undefined;
|
|
3102
|
+
let cassowaryConstraint = constraint._cassowaryConstraint;
|
|
3103
|
+
attemptedCassowaryConstraints.push(cassowaryConstraint);
|
|
3104
|
+
solver.addConstraint(cassowaryConstraint);
|
|
3105
|
+
constraint._solver = solver;
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
break;
|
|
3110
|
+
}
|
|
3111
|
+
catch (e) {
|
|
3112
|
+
|
|
3113
|
+
// If an error is thrown, attempt to re-do the layout by removing the offending constraint
|
|
3114
|
+
console.error(`[BMCoreUI] Unsatisfiable constraints. The following constraints are not satisfiable and the
|
|
3115
|
+
layout operation cannot be completed. Will attempt to solve the layout by breaking constraint
|
|
3116
|
+
${lastConstraint}. Please check the following constraints at design-time to resolve the problem:\n`);
|
|
3117
|
+
|
|
3118
|
+
attemptedConstraints.forEach(constraint => {
|
|
3119
|
+
console.error(`${constraint}`);
|
|
3120
|
+
});
|
|
3121
|
+
|
|
3122
|
+
// At design-time, the process should stop in its tracks
|
|
3123
|
+
if (this._layoutEditor) return;
|
|
3124
|
+
|
|
3125
|
+
constraintsToIgnore = constraintsToIgnore || [];
|
|
3126
|
+
constraintsToIgnore.push(lastConstraint);
|
|
3127
|
+
|
|
3128
|
+
// Remove all previously attempted constraints and re-try
|
|
3129
|
+
attemptedCassowaryConstraints.forEach(constraint => {
|
|
3130
|
+
if (solver.hasConstraint(constraint)) solver.removeConstraint(constraint);
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
// TODO - verify
|
|
3134
|
+
|
|
3135
|
+
//return this.layoutSubviews();
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
// Suggest the origin point for the root view's position
|
|
3141
|
+
try {
|
|
3142
|
+
solver.suggestValue(this._variables[BMLayoutAttribute.Top], 0);
|
|
3143
|
+
}
|
|
3144
|
+
catch (e) {
|
|
3145
|
+
// If dual optimize fails, the intrinsic sizes lead to an unsolable layout
|
|
3146
|
+
console.log(e);
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
// Suggest values for the intrinsic sizes
|
|
3150
|
+
allSubviews.forEach(subview => {
|
|
3151
|
+
if (subview._heightCompressionConstraint) try {
|
|
3152
|
+
solver.suggestValue(subview._variables[BMLayoutAttribute.Height], subview._heightCompressionConstraint._constant);
|
|
3153
|
+
}
|
|
3154
|
+
catch (e) {
|
|
3155
|
+
console.log(e);
|
|
3156
|
+
}
|
|
3157
|
+
});
|
|
3158
|
+
|
|
3159
|
+
// The solver is now prepared to update the variables
|
|
3160
|
+
solver.updateVariables();
|
|
3161
|
+
|
|
3162
|
+
// All variables should now be set. New frames can be created and applied to all views
|
|
3163
|
+
this.frame = BMRectMake(0, 0, this._variables[BMLayoutAttribute.Width].value() | 0, this._variables[BMLayoutAttribute.Height].value() | 0);
|
|
3164
|
+
this._needsIntrinsicSizeMeasurement = NO;
|
|
3165
|
+
|
|
3166
|
+
if (this.supportsAutomaticIntrinsicSize && !this._subviews.length) {
|
|
3167
|
+
this._requiredWidth = undefined;
|
|
3168
|
+
this._needsLayout = NO;
|
|
3169
|
+
this._needsIntrinsicSizeMeasurement = NO;
|
|
3170
|
+
}
|
|
3171
|
+
else {
|
|
3172
|
+
this._subviews.forEach(subview => subview._updateFrames());
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
if (BMViewDebug) console.log(`
|
|
3176
|
+
***********************************************************************************************************
|
|
3177
|
+
[BMCoreUI] Layout pass finished
|
|
3178
|
+
***********************************************************************************************************
|
|
3179
|
+
`);
|
|
3180
|
+
|
|
3181
|
+
},
|
|
3182
|
+
|
|
3183
|
+
/**
|
|
3184
|
+
* Invoked by CoreUI at the end of the layout pass on the root view of a hierarchy to obtain the frame it should assign
|
|
3185
|
+
* to a descendant view.
|
|
3186
|
+
*
|
|
3187
|
+
* The root view must provide a valid frame to assign to the view. The default implementation returns a frame that is the result
|
|
3188
|
+
* of the resolved layout equations.
|
|
3189
|
+
*
|
|
3190
|
+
* Subclasses can override this method to provide different frames for descendants.
|
|
3191
|
+
*
|
|
3192
|
+
* @param descendant <BMView> The subview.
|
|
3193
|
+
* @return <BMRect> The frame to assign.
|
|
3194
|
+
*/
|
|
3195
|
+
frameForDescendant(descendant) {
|
|
3196
|
+
// Only assign the frame if this view has opted into CoreUI layout and has a deterministic layout
|
|
3197
|
+
if ((descendant._constraints.length || descendant._internalConstraints.length) && descendant._hasDeterministicConstraints) {
|
|
3198
|
+
return BMRectMake(
|
|
3199
|
+
descendant._variables[BMLayoutAttribute.Left].value() | 0,
|
|
3200
|
+
descendant._variables[BMLayoutAttribute.Top].value() | 0,
|
|
3201
|
+
descendant._variables[BMLayoutAttribute.Width].value() | 0,
|
|
3202
|
+
descendant._variables[BMLayoutAttribute.Height].value() | 0
|
|
3203
|
+
);
|
|
3204
|
+
}
|
|
3205
|
+
else {
|
|
3206
|
+
// Otherwise return to the reference frame
|
|
3207
|
+
return descendant._referenceFrame;
|
|
3208
|
+
}
|
|
3209
|
+
},
|
|
3210
|
+
|
|
3211
|
+
/**
|
|
3212
|
+
* Invoked as the final step of the layout process. Extracts the result from the
|
|
3213
|
+
* Cassowary equation and creates the final frames for this view and all of its subviews.
|
|
3214
|
+
*/
|
|
3215
|
+
_updateFrames() {
|
|
3216
|
+
// When the frame is assigned, the assigned width is cleared
|
|
3217
|
+
// to allow future intrinsic size calculations to work correctly
|
|
3218
|
+
this._requiredWidth = undefined;
|
|
3219
|
+
this._needsLayout = NO;
|
|
3220
|
+
this._needsIntrinsicSizeMeasurement = NO;
|
|
3221
|
+
|
|
3222
|
+
this.frame = this.rootView.frameForDescendant(this);
|
|
3223
|
+
|
|
3224
|
+
// Update the frames of subviews recursively as well
|
|
3225
|
+
this._subviews.forEach(subview => subview._updateFrames());
|
|
3226
|
+
},
|
|
3227
|
+
|
|
3228
|
+
/**
|
|
3229
|
+
* A property indicating whether this view's layout is stale and the view should be laid out again.
|
|
3230
|
+
*
|
|
3231
|
+
* Setting this property to <code>YES</code> will cause the superview managing this view's layout to recalculate the layout
|
|
3232
|
+
* during the next animation frame. Afterwards, this property will be reset to NO.
|
|
3233
|
+
*
|
|
3234
|
+
* Setting this property to <code>NO</code> has no effect, however the value of this property may be used to determine whether
|
|
3235
|
+
* this view's layout needs to be recalculated.
|
|
3236
|
+
*/
|
|
3237
|
+
_needsLayout: NO, // <Boolean>
|
|
3238
|
+
get needsLayout() {
|
|
3239
|
+
return this._needsLayout;
|
|
3240
|
+
},
|
|
3241
|
+
set needsLayout(needs) {
|
|
3242
|
+
this._needsLayout = needs;
|
|
3243
|
+
if (needs) {
|
|
3244
|
+
this.rootView._needsLayout = YES;
|
|
3245
|
+
this.rootView._scheduleLayout();
|
|
3246
|
+
}
|
|
3247
|
+
},
|
|
3248
|
+
|
|
3249
|
+
// #endregion
|
|
3250
|
+
|
|
3251
|
+
// #region Subview Management
|
|
3252
|
+
|
|
3253
|
+
/**
|
|
3254
|
+
* The topmost superview managing the layout for this hierarchy. This may be this view itself.
|
|
3255
|
+
* Note that a view hierarchy may have several root views each managing their own local layouts.
|
|
3256
|
+
* A view may be both a root view for its own layout and a child view of another layout.
|
|
3257
|
+
*/
|
|
3258
|
+
_rootView: undefined, // <BMView>
|
|
3259
|
+
get rootView() {
|
|
3260
|
+
var superview = this;
|
|
3261
|
+
while (superview) {
|
|
3262
|
+
if (!superview.superview) return superview;
|
|
3263
|
+
superview = superview.superview;
|
|
3264
|
+
}
|
|
3265
|
+
},
|
|
3266
|
+
|
|
3267
|
+
/**
|
|
3268
|
+
* Returns <code>YES</code> if this view is a root view that manages the layout of its descendants.
|
|
3269
|
+
*/
|
|
3270
|
+
get isRootView() { // <Boolean>
|
|
3271
|
+
return this.superview === undefined;
|
|
3272
|
+
},
|
|
3273
|
+
|
|
3274
|
+
/**
|
|
3275
|
+
* The superview containing this view, if available.
|
|
3276
|
+
*/
|
|
3277
|
+
_superview: undefined, // <BMView, nullable>
|
|
3278
|
+
get superview() {
|
|
3279
|
+
return this._superview;
|
|
3280
|
+
},
|
|
3281
|
+
|
|
3282
|
+
/**
|
|
3283
|
+
* An array of views contained by this view.
|
|
3284
|
+
* You must not modify the array returned by this property.
|
|
3285
|
+
*/
|
|
3286
|
+
_subviews: undefined, // <[BMView]>
|
|
3287
|
+
get subviews() {
|
|
3288
|
+
return this._subviews.slice();
|
|
3289
|
+
},
|
|
3290
|
+
|
|
3291
|
+
/**
|
|
3292
|
+
* Makes this view a child of the given superview. The DOM node managed by this view will be moved to
|
|
3293
|
+
* the superview's <code>contentNode</code>.
|
|
3294
|
+
* If this view already has a superview, it will first be removed from its current superview.
|
|
3295
|
+
* @param superview <BMView> The view which will become this view's superview.
|
|
3296
|
+
* {
|
|
3297
|
+
* @param toPosition <Number, nullable> Defaults to the last available position within the superview. The position
|
|
3298
|
+
* in which to add this view to the superview. If this is specified
|
|
3299
|
+
* CoreUI will add the view's node before the node of the
|
|
3300
|
+
* view currently occupying that position.
|
|
3301
|
+
* }
|
|
3302
|
+
*/
|
|
3303
|
+
addToSuperview(superview, args) {
|
|
3304
|
+
return superview.addSubview(this, args);
|
|
3305
|
+
},
|
|
3306
|
+
|
|
3307
|
+
/**
|
|
3308
|
+
* Should be invoked to add a subview to this view's <code>contentNode</code>. If that subview
|
|
3309
|
+
* already has a superview, it will first be removed from its current superview.
|
|
3310
|
+
* @param subview <BMView> The subview to add.
|
|
3311
|
+
* {
|
|
3312
|
+
* @param toPosition <Number, nullable> Defaults to the last available position within this view. The position
|
|
3313
|
+
* in which to add the given subview. If this is specified
|
|
3314
|
+
* CoreUI will add the subview's node before the node of the
|
|
3315
|
+
* view currently occupying that position.
|
|
3316
|
+
* }
|
|
3317
|
+
*/
|
|
3318
|
+
addSubview(subview, args) {
|
|
3319
|
+
// If that subview is already added to this view, don't do anything else
|
|
3320
|
+
if (subview.superview == this) return;
|
|
3321
|
+
|
|
3322
|
+
// Mark this view as a root view if it has no superview
|
|
3323
|
+
if (!this.superview) BMView._markAsRootView(this);
|
|
3324
|
+
|
|
3325
|
+
// Mark the newly added view as a subview
|
|
3326
|
+
BMView._markAsNonRootView(subview);
|
|
3327
|
+
|
|
3328
|
+
// Add the subview to the subviews array
|
|
3329
|
+
var position = args && args.toPosition;
|
|
3330
|
+
if (position === undefined) position = this._subviews.length;
|
|
3331
|
+
|
|
3332
|
+
// The target view represents the view whose position the new subview
|
|
3333
|
+
// will occupy instead
|
|
3334
|
+
var targetView = this._subviews[position];
|
|
3335
|
+
|
|
3336
|
+
this._subviews.splice(position, 0, subview);
|
|
3337
|
+
|
|
3338
|
+
// Detach the subview from its superview if available
|
|
3339
|
+
if (subview.superview) subview.superview._detachSubview(subview);
|
|
3340
|
+
subview._superview = this;
|
|
3341
|
+
|
|
3342
|
+
// Atach the subview's node to this view's content node
|
|
3343
|
+
if (targetView) {
|
|
3344
|
+
// If the target view is no longer part of the content node, insert the subview at the
|
|
3345
|
+
// target position with the DOM structure, if possible, otherwise default to inserting it
|
|
3346
|
+
// at the end of the content node's DOM structure
|
|
3347
|
+
if (targetView.node.parentNode != this.contentNode) {
|
|
3348
|
+
let targetNode = this.contentNode.children[position];
|
|
3349
|
+
if (targetNode) {
|
|
3350
|
+
this.contentNode.insertBefore(subview.node, targetNode);
|
|
3351
|
+
}
|
|
3352
|
+
else {
|
|
3353
|
+
this.contentNode.appendChild(subview.node);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
else {
|
|
3357
|
+
this.contentNode.insertBefore(subview.node, targetView.node);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
else {
|
|
3361
|
+
if (subview.node.parentNode != this.contentNode) {
|
|
3362
|
+
this.contentNode.appendChild(subview.node);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
// Invalidate the root view's constraints
|
|
3367
|
+
const rootView = this.rootView;
|
|
3368
|
+
rootView._invalidatedConstraints = YES;
|
|
3369
|
+
rootView._invalidatedSizeClasses = YES;
|
|
3370
|
+
rootView.needsLayout = YES;
|
|
3371
|
+
},
|
|
3372
|
+
|
|
3373
|
+
/**
|
|
3374
|
+
* Removes the given subview from this view. Invoking this method has no effect
|
|
3375
|
+
* if the given view is not a subview of this view.
|
|
3376
|
+
* The view's DOM node will also be detached from the document if it is a direct descendant of this view's `contentNode`.
|
|
3377
|
+
* @param subview <BMView> The view to remove.
|
|
3378
|
+
*/
|
|
3379
|
+
removeSubview(subview) {
|
|
3380
|
+
var index = this._subviews.indexOf(subview);
|
|
3381
|
+
if (index != -1) {
|
|
3382
|
+
this._subviews.splice(index, 1);
|
|
3383
|
+
if (subview._node.parentNode == this.contentNode) {
|
|
3384
|
+
subview._node.remove();
|
|
3385
|
+
}
|
|
3386
|
+
subview._superview = undefined;
|
|
3387
|
+
|
|
3388
|
+
// Because constraints are duplicated for both the target and source views, this method only needs to be invoked once
|
|
3389
|
+
// to clear out constraints for both view hierarchies
|
|
3390
|
+
this._removeInvalidConstraints();
|
|
3391
|
+
|
|
3392
|
+
// If this view no longer contains any subviews, remove it from the superview map
|
|
3393
|
+
if (!this._subviews.length) BMView._markAsNonRootView(this);
|
|
3394
|
+
|
|
3395
|
+
// Make the newly detached subview a root view if it has subviews
|
|
3396
|
+
if (subview._subviews.length) BMView._markAsRootView(subview);
|
|
3397
|
+
|
|
3398
|
+
// Invalidate the root view's constraints
|
|
3399
|
+
const rootView = this.rootView;
|
|
3400
|
+
rootView._invalidatedConstraints = YES;
|
|
3401
|
+
rootView._invalidatedSizeClasses = YES;
|
|
3402
|
+
rootView.needsLayout = YES;
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
},
|
|
3406
|
+
|
|
3407
|
+
/**
|
|
3408
|
+
* Removes this given subview from its superview. Invoking this method has no effect
|
|
3409
|
+
* if the given view does not have a superview.
|
|
3410
|
+
* The view's DOM node will also be detached from the document if it is a direct descendant of the superview's `contentNode`.
|
|
3411
|
+
*/
|
|
3412
|
+
removeFromSuperview() {
|
|
3413
|
+
if (this._superview) {
|
|
3414
|
+
this._superview.removeSubview(this);
|
|
3415
|
+
}
|
|
3416
|
+
},
|
|
3417
|
+
|
|
3418
|
+
/**
|
|
3419
|
+
* Returns `YES` if this view is a descendant of the given view, `NO` otherwise.
|
|
3420
|
+
* @param view <BMView> The parent view.
|
|
3421
|
+
* @return <Boolean> `YES` if this view is a descendant of the given view, `NO` otherwise.
|
|
3422
|
+
*/
|
|
3423
|
+
isDescendantOfView(view) {
|
|
3424
|
+
let superview = this.superview;
|
|
3425
|
+
while (superview) {
|
|
3426
|
+
if (superview == view) return YES;
|
|
3427
|
+
superview = superview.superview;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
return NO;
|
|
3431
|
+
},
|
|
3432
|
+
|
|
3433
|
+
/**
|
|
3434
|
+
* Invoked by CoreUI after a view is removed from its superview to remove constraints affecting views that are no longer part of the
|
|
3435
|
+
* same view hierarchy.
|
|
3436
|
+
*/
|
|
3437
|
+
_removeInvalidConstraints() {
|
|
3438
|
+
let constraints = new Set;
|
|
3439
|
+
|
|
3440
|
+
// Filter out the constraints to remove duplicates
|
|
3441
|
+
this.activeConstraints.forEach(constraint => {
|
|
3442
|
+
constraints.add(constraint);
|
|
3443
|
+
});
|
|
3444
|
+
|
|
3445
|
+
constraints.forEach(constraint => {
|
|
3446
|
+
if (constraint.isConstraintCollection) {
|
|
3447
|
+
// For constraint collections, remove them whenever any of their constituent constraints affect two views
|
|
3448
|
+
// that are no longer part of the same view hierarchy
|
|
3449
|
+
for (let constituentConstraint of constraint.constituentConstraints) {
|
|
3450
|
+
if (constituentConstraint._targetView && constituentConstraint._sourceView.rootView != constituentConstraint._targetView.rootView) {
|
|
3451
|
+
constraint.remove();
|
|
3452
|
+
break;
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
else {
|
|
3457
|
+
// Remove those constraints that affect two views that are now part of different view hierarchies
|
|
3458
|
+
if (constraint._targetView && constraint._sourceView.rootView != constraint._targetView.rootView) {
|
|
3459
|
+
constraint.remove();
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
});
|
|
3463
|
+
},
|
|
3464
|
+
|
|
3465
|
+
/**
|
|
3466
|
+
* Invoked internally by CoreUI when the given subview is about to move to a different superview.
|
|
3467
|
+
* Removes the subview from this view, but does not detach its DOM node from the document.
|
|
3468
|
+
* @param subview <BMView> The subview to detach.
|
|
3469
|
+
*/
|
|
3470
|
+
_detachSubview(subview) {
|
|
3471
|
+
var index = this._subviews.indexOf(subview);
|
|
3472
|
+
if (index != -1) {
|
|
3473
|
+
this._subviews.splice(index, 1);
|
|
3474
|
+
}
|
|
3475
|
+
},
|
|
3476
|
+
|
|
3477
|
+
/**
|
|
3478
|
+
* Returns a flat array containg all of the views within this view hierarchy.
|
|
3479
|
+
*/
|
|
3480
|
+
get allSubviews() { // <[BMView]>
|
|
3481
|
+
let result = [this];
|
|
3482
|
+
|
|
3483
|
+
this._subviews.forEach(subview => result = result.concat(subview.allSubviews));
|
|
3484
|
+
|
|
3485
|
+
return result;
|
|
3486
|
+
},
|
|
3487
|
+
|
|
3488
|
+
// #endregion
|
|
3489
|
+
|
|
3490
|
+
// #region Cloning
|
|
3491
|
+
|
|
3492
|
+
/**
|
|
3493
|
+
* Prepares this view hierarchy for cloning.
|
|
3494
|
+
*/
|
|
3495
|
+
_prepareForCloning() {
|
|
3496
|
+
this._BMTempID = BMUUIDMake();
|
|
3497
|
+
this._BMOriginalID = this.node.id;
|
|
3498
|
+
|
|
3499
|
+
this.node.id = this._BMTempID;
|
|
3500
|
+
|
|
3501
|
+
this._subviews.each(subview => subview._prepareForCloning());
|
|
3502
|
+
},
|
|
3503
|
+
|
|
3504
|
+
/**
|
|
3505
|
+
* Returns a clone of this view hierarchy and its constraints.
|
|
3506
|
+
* @return <BMView> A clone of this view.
|
|
3507
|
+
*/
|
|
3508
|
+
_clone() {
|
|
3509
|
+
this._prepareForCloning();
|
|
3510
|
+
|
|
3511
|
+
let clonedNode = this.node.cloneNode(YES);
|
|
3512
|
+
|
|
3513
|
+
let clone = this._cloneForNode(clonedNode);
|
|
3514
|
+
},
|
|
3515
|
+
|
|
3516
|
+
/**
|
|
3517
|
+
* Returns a clone of this view initialized to the given
|
|
3518
|
+
* node.
|
|
3519
|
+
* @param node <DOMNode> The cloned node corresponding to this view.
|
|
3520
|
+
* {
|
|
3521
|
+
* @param superview <BMView, nullable> The clone of this view's superview, if there is one.
|
|
3522
|
+
* }
|
|
3523
|
+
* @return <BMView> A view.
|
|
3524
|
+
*/
|
|
3525
|
+
_cloneForNode(clonedNode, args) {
|
|
3526
|
+
let clone = BMView.viewForNode.call(this.constructor, clonedNode);
|
|
3527
|
+
clone._BMOriginalView = this;
|
|
3528
|
+
|
|
3529
|
+
// Restore the node's original ID
|
|
3530
|
+
this.node.id = this._BMOriginalID;
|
|
3531
|
+
},
|
|
3532
|
+
|
|
3533
|
+
// #endregion
|
|
3534
|
+
|
|
3535
|
+
// #region Layout Editor
|
|
3536
|
+
|
|
3537
|
+
/**
|
|
3538
|
+
* When this view is edited by a layout editor, this method will be invoked when constructing the settings panel for this view.
|
|
3539
|
+
* View subclasses can override this method to provide additional tabs to add to this view's settings panel.
|
|
3540
|
+
*
|
|
3541
|
+
* Subclasses should not add setting sections through this method. After this method returns, the layout editor will subsequently
|
|
3542
|
+
* invoke `additionalSettingSectionsForTab(_, {layoutEditor})` for each standard tab as well as each tab that has been returned by this method.
|
|
3543
|
+
*
|
|
3544
|
+
* The default implementation returns an empty array.
|
|
3545
|
+
* @param editor <BMLayoutEditor> The caller.
|
|
3546
|
+
* @return <[BMLayoutEditorSettingsTab]> An array of settings tabs to add.
|
|
3547
|
+
*/
|
|
3548
|
+
additionalSettingTabsForLayoutEditor(editor) {
|
|
3549
|
+
return [];
|
|
3550
|
+
},
|
|
3551
|
+
|
|
3552
|
+
/**
|
|
3553
|
+
* When this view is edited by a layout editor, this method will be invoked when constructing the settings panel for this view.
|
|
3554
|
+
* This method will also be invoked whenever the settings for the given tab have been invalidated.
|
|
3555
|
+
* View subclasses can override this method to provide additional settings to specific tabs.
|
|
3556
|
+
*
|
|
3557
|
+
* The default implementation returns an empty array for all tabs.
|
|
3558
|
+
* @param tab <BMLayoutEditorSettingsTab> The tab for which to supply additional settings.
|
|
3559
|
+
* {
|
|
3560
|
+
* @param layoutEditor <BMLayoutEditor> The caller.
|
|
3561
|
+
* }
|
|
3562
|
+
* @return <[BMLayoutEditorSettingsSection]> An array of settings tabs to add.
|
|
3563
|
+
*/
|
|
3564
|
+
additionalSettingSectionsForTab(tab, {layoutEditor}) {
|
|
3565
|
+
return [];
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
// #endregion
|
|
3569
|
+
|
|
3570
|
+
});
|
|
3571
|
+
|
|
3572
|
+
// @endtype
|