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,2000 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {YES, NO, BMExtend, BMCopyProperties} from '../Core/BMCoreUI'
|
|
4
|
+
import {BMPointMake} from '../Core/BMPoint'
|
|
5
|
+
import {BMRectMake, BMRectMakeWithNodeFrame} from '../Core/BMRect'
|
|
6
|
+
import {BMAnimateWithBlock, BMAnimationContextGetCurrent, BMAnimationContextEnableWebAnimations, __BMVelocityAnimate, BMHook} from '../Core/BMAnimationContext'
|
|
7
|
+
import {BMView} from '../BMView/BMView_v2.5'
|
|
8
|
+
import {BMLayoutOrientation} from '../BMView/BMLayoutSizeClass'
|
|
9
|
+
import { BMKeyboardShortcutModifier } from './BMKeyboardShortcut'
|
|
10
|
+
import { BMSizeMake } from '../Core/BMSize'
|
|
11
|
+
import { BMLayoutAttribute } from '../BMView/BMLayoutConstraint_v2.5'
|
|
12
|
+
|
|
13
|
+
//@ts-check
|
|
14
|
+
// @type BMWindowOverlay extends BMView
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Window overlays are objects that are created by windows to mask the content below the window
|
|
18
|
+
* and allow clicking outside a window to close it.
|
|
19
|
+
*/
|
|
20
|
+
class BMWindowOverlay extends BMView {
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initializes this window overlay for the given window.
|
|
24
|
+
* @param window <BMWindow> The window.
|
|
25
|
+
* {
|
|
26
|
+
* @param modal <Boolean, nullable> Defaults to `YES`. If set to `YES`, the blocker will prevent events from reaching elements behind it.
|
|
27
|
+
* When set to `NO`, it will allow events to propagate to those elements.
|
|
28
|
+
* }
|
|
29
|
+
* @return <BMWindowOverlay> This window overlay.
|
|
30
|
+
*/
|
|
31
|
+
initWithWindow(window, args) {
|
|
32
|
+
this._window = window;
|
|
33
|
+
|
|
34
|
+
let modal = (args && 'modal' in args) ? args.modal : YES;
|
|
35
|
+
|
|
36
|
+
let node = document.createElement('div');
|
|
37
|
+
node.className = 'BMWindowBlocker';
|
|
38
|
+
// TODO: This isn't good for subclasses; consider using a class instead
|
|
39
|
+
BMCopyProperties(node.style, {
|
|
40
|
+
opacity: 0,
|
|
41
|
+
display: 'none',
|
|
42
|
+
backdropFilter: modal ? 'blur(15px)' : 'none',
|
|
43
|
+
webkitBackdropFilter: modal ? 'blur(15px)' : 'none'
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!modal) {
|
|
47
|
+
node.style.pointerEvents = 'none';
|
|
48
|
+
node.classList.add('BMWindowBlockerNonModal');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (modal) node.addEventListener('click', event => {
|
|
52
|
+
// Do not close if the click originated from inside the window.
|
|
53
|
+
if (event.target != node) return;
|
|
54
|
+
|
|
55
|
+
var shouldClose = YES;
|
|
56
|
+
|
|
57
|
+
if (window.delegate && window.delegate.windowShouldClose) {
|
|
58
|
+
shouldClose = window.delegate.windowShouldClose(window);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var targetRect = undefined;
|
|
62
|
+
var targetNode = undefined;
|
|
63
|
+
|
|
64
|
+
if (shouldClose) {
|
|
65
|
+
// Don't ask the delegate for an anchor if it has been set via properties
|
|
66
|
+
if (window._anchorPoint || window._anchorRect || window._anchorNode) {
|
|
67
|
+
window.dismissAnimated(YES);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (window.delegate && window.delegate.rectForDismissedWindow) {
|
|
71
|
+
targetRect = window.delegate.rectForDismissedWindow(window);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (window.delegate && window.delegate.DOMNodeForDismissedWindow) {
|
|
75
|
+
targetNode = window.delegate.DOMNodeForDismissedWindow(window);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
window.dismissAnimated(YES, {toRect: targetRect, toNode: targetNode});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return super.initWithDOMNode(node);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// @override - BMView
|
|
87
|
+
frameForDescendant(descendant) {
|
|
88
|
+
return this._window.frameForDescendant(descendant);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// @override - BMView
|
|
92
|
+
_registerLayout() {
|
|
93
|
+
if (this._window._visible) {
|
|
94
|
+
super._registerLayout();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this._awaitsLayout = YES;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Invoked when this overlay's window will become visible.
|
|
103
|
+
*/
|
|
104
|
+
_windowWillAppear() {
|
|
105
|
+
if (this._awaitsLayout) {
|
|
106
|
+
this._awaitsLayout = NO;
|
|
107
|
+
super._registerLayout();
|
|
108
|
+
this.layoutQueue.dequeue();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// @override - BMView
|
|
114
|
+
get _currentViewport() {
|
|
115
|
+
if (this._requiredViewport) return this._requiredViewport;
|
|
116
|
+
|
|
117
|
+
let viewport = new BMViewport;
|
|
118
|
+
viewport.init();
|
|
119
|
+
|
|
120
|
+
viewport._width = this._window._fullScreen ? window.innerWidth : this._window.frame.size.width;
|
|
121
|
+
viewport._height = this._window._fullScreen ? window.innerHeight : this._window.frame.size.height;
|
|
122
|
+
viewport._diagonal = Math.sqrt(Math.pow(viewport._width, 2) + Math.pow(viewport._height, 2));
|
|
123
|
+
viewport._orientation = viewport._width >= viewport._height ? BMLayoutOrientation.Landscape : BMLayoutOrientation.Portrait;
|
|
124
|
+
viewport._surfaceArea = viewport._width * viewport._height;
|
|
125
|
+
|
|
126
|
+
return viewport;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// @endtype
|
|
132
|
+
|
|
133
|
+
// @type BMWindow extends BMView
|
|
134
|
+
|
|
135
|
+
var _BMWindowAnimationDurationDefault = 400;
|
|
136
|
+
var _BMWindowAnimationEasingDefault = 'easeInOutQuart';
|
|
137
|
+
const _BMWindowAnimationEasingIncomingDefault = 'easeOutQuart';
|
|
138
|
+
const _BMWindowAnimationDurationIncomingDefault = 200;
|
|
139
|
+
const _BMWindowAnimationEasingOutgoingDefault = 'easeInQuart';
|
|
140
|
+
const _BMWindowAnimationDurationOutgoingDefault = 200;
|
|
141
|
+
|
|
142
|
+
const BM_WINDOW_SHOWCASE_MAX_ITERATIONS = 5000;
|
|
143
|
+
var BM_WINDOW_Z_INDEX_MAX = 2007;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A window is an object that manages the display and lifecycle of a popup window.
|
|
147
|
+
*/
|
|
148
|
+
export function BMWindow() {}; // <constructor>
|
|
149
|
+
|
|
150
|
+
// Represents the current key window
|
|
151
|
+
BMWindow._keyWindow = undefined;
|
|
152
|
+
|
|
153
|
+
// An array containing all created non-modal windows
|
|
154
|
+
BMWindow._windows = [];
|
|
155
|
+
|
|
156
|
+
// An array containing all minimized non-modal window DOM nodes
|
|
157
|
+
BMWindow._minimizedWindows = [];
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Returns the z-index value that the topmost non-modal window will have.
|
|
162
|
+
* Any z-index value higher than the value returned by this method is guaranteed
|
|
163
|
+
* to appear on top of any other window.
|
|
164
|
+
*
|
|
165
|
+
* Note that the value used by modal windows is always at least `BM_WINDOW_Z_INDEX_MAX + 1`.
|
|
166
|
+
* @return <Number> The maximum z-index value used by a non-modal `BMWindow`.
|
|
167
|
+
*/
|
|
168
|
+
BMWindow.zIndexMax = function () {
|
|
169
|
+
return BM_WINDOW_Z_INDEX_MAX;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Minimizes all non-minimized windows.
|
|
174
|
+
* @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated.
|
|
175
|
+
*/
|
|
176
|
+
BMWindow.minimizeAllAnimated = function (animated) {
|
|
177
|
+
for (let window of this._windows.slice()) {
|
|
178
|
+
if (!window._minimizedWindow) {
|
|
179
|
+
window.minimizeAnimated(animated);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Restores all minimized windows.
|
|
186
|
+
* @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated.
|
|
187
|
+
*/
|
|
188
|
+
BMWindow.restoreAllAnimated = function (animated) {
|
|
189
|
+
for (let window of this._windows.slice()) {
|
|
190
|
+
if (window._minimizedWindow) {
|
|
191
|
+
window.restoreAnimated(animated);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Reveals all hidden windows.
|
|
198
|
+
*/
|
|
199
|
+
BMWindow.revealAll = function () {
|
|
200
|
+
for (let window of this._windows.slice()) {
|
|
201
|
+
if (window._hidden) {
|
|
202
|
+
window.reveal();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Hides all windows.
|
|
209
|
+
*/
|
|
210
|
+
BMWindow.hideAll = function () {
|
|
211
|
+
for (let window of this._windows.slice()) {
|
|
212
|
+
if (!window._hidden) {
|
|
213
|
+
window.hide();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Set to `YES` while showcase mode is enabled.
|
|
220
|
+
*/
|
|
221
|
+
BMWindow._isShowcasing = NO;
|
|
222
|
+
|
|
223
|
+
// Adds a global handler that listens for cmd + alt + a to enter or exit showcase
|
|
224
|
+
document.addEventListener('keydown', event => {
|
|
225
|
+
if (event.which == 69 && event.altKey && (navigator.userAgent.includes('Macintosh') ? event.metaKey : event.ctrlKey)) {
|
|
226
|
+
if (BMWindow._isShowcasing) {
|
|
227
|
+
BMWindow.exitShowcase();
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
BMWindow.enterShowcase();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}, YES);
|
|
234
|
+
|
|
235
|
+
BMWindow._showcaseElements = [];
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Registers an element that will participate in the window showcase.
|
|
239
|
+
* @param element <AnyObject> The showcase element.
|
|
240
|
+
*/
|
|
241
|
+
BMWindow.registerShowcaseElement = function (element) {
|
|
242
|
+
BMWindow._showcaseElements.push(element);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Unregisters a showcase element.
|
|
248
|
+
* @param element <AnyObject> The showcase element.
|
|
249
|
+
*/
|
|
250
|
+
BMWindow.unregisterShowcaseElement = function (element) {
|
|
251
|
+
BMWindow._showcaseElements.splice(BMWindow._showcaseElements.indexOf(element), 1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Enters showcase mode, making it easy to select between open windows.
|
|
256
|
+
*/
|
|
257
|
+
BMWindow.enterShowcase = async function () {
|
|
258
|
+
if (BMWindow._isShowcasing) return;
|
|
259
|
+
|
|
260
|
+
document.body.style.backgroundColor = 'rgb(60, 60, 60)';
|
|
261
|
+
|
|
262
|
+
BMWindow._isShowcasing = YES;
|
|
263
|
+
|
|
264
|
+
document.body.addEventListener('click', BMWindow.exitShowcase);
|
|
265
|
+
|
|
266
|
+
let openWindows = BMWindow._windows.filter(value => !value._minimizedWindow);
|
|
267
|
+
|
|
268
|
+
let viewportFrame = BMRectMake(0, 0, window.innerWidth, window.innerHeight);
|
|
269
|
+
|
|
270
|
+
for (let element of BMWindow._showcaseElements) {
|
|
271
|
+
openWindows.unshift(element);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
openWindows.forEach(window => {
|
|
275
|
+
window.node.classList.add('BMWindowShowcaseWindow');
|
|
276
|
+
|
|
277
|
+
let selector = document.createElement('div');
|
|
278
|
+
selector.classList.add('BMWindowShowcaseWindowSelector');
|
|
279
|
+
selector.addEventListener('click', event => {
|
|
280
|
+
if (window.becomeKeyWindow) {
|
|
281
|
+
window.becomeKeyWindow();
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
BMWindow.hideAll();
|
|
285
|
+
}
|
|
286
|
+
BMWindow.exitShowcase();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
window.node.appendChild(selector);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
let sourceRects = openWindows.map(window => window._fullScreen ? viewportFrame.copy() : window.frame.copy());
|
|
293
|
+
let originalRects = sourceRects.map(rect => rect.copy());
|
|
294
|
+
let overlap;
|
|
295
|
+
|
|
296
|
+
let bounds = viewportFrame.copy();
|
|
297
|
+
|
|
298
|
+
for (let i = 0; i < BM_WINDOW_SHOWCASE_MAX_ITERATIONS; i++) {
|
|
299
|
+
|
|
300
|
+
for (let j = 0; j < sourceRects.length; j++) {
|
|
301
|
+
for (let k = 0; k < sourceRects.length; k++) {
|
|
302
|
+
if (j != k && sourceRects[j].intersectsRect(sourceRects[k])) {
|
|
303
|
+
overlap = YES;
|
|
304
|
+
|
|
305
|
+
let rect1 = sourceRects[j];
|
|
306
|
+
let rect2 = sourceRects[k];
|
|
307
|
+
|
|
308
|
+
let difference = rect2.center.pointBySubtractingPoint(rect1.center);
|
|
309
|
+
|
|
310
|
+
if (!difference.x && !difference.y) {
|
|
311
|
+
difference.x = Math.random() * 2 - 1;
|
|
312
|
+
difference.y = Math.random() * 2 - 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (bounds.height / bounds.width > viewportFrame.height / viewportFrame.width) {
|
|
316
|
+
difference.x = difference.x * 2;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
difference.y = difference.y * 2;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let length = Math.sqrt(difference.x * difference.x + difference.y * difference.y);
|
|
323
|
+
difference.x = difference.x * 20 / length;
|
|
324
|
+
difference.y = difference.y * 20 / length;
|
|
325
|
+
|
|
326
|
+
rect1.offsetWithX(-difference.x, {y: -difference.y});
|
|
327
|
+
rect2.offsetWithX(difference.x, {y: difference.y});
|
|
328
|
+
|
|
329
|
+
bounds = bounds.rectByUnionWithRect(rect1).rectByUnionWithRect(rect2);
|
|
330
|
+
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!overlap) break;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let scale = Math.min(1, viewportFrame.width / bounds.width, viewportFrame.height / bounds.height);
|
|
339
|
+
|
|
340
|
+
for (let rect of sourceRects) {
|
|
341
|
+
rect.offset(-bounds.left, -bounds.top);
|
|
342
|
+
|
|
343
|
+
rect.origin.x = rect.origin.x * scale;
|
|
344
|
+
rect.origin.y = rect.origin.y * scale;
|
|
345
|
+
|
|
346
|
+
rect.size.width = rect.size.width * scale;
|
|
347
|
+
rect.size.height = rect.size.height * scale;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// This method appears to be faster when used with web animations, so they are always enabled here when available
|
|
351
|
+
BMAnimateWithBlock(() => {
|
|
352
|
+
BMAnimationContextEnableWebAnimations();
|
|
353
|
+
|
|
354
|
+
/*let header = document.getElementById('twStudioHeader');
|
|
355
|
+
let headerController = BMAnimationContextGetCurrent().controllerForObject(header, {node: header});
|
|
356
|
+
headerController.registerBuiltInProperty('translateY', {withValue: -header.offsetHeight + 'px'});*/
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < sourceRects.length; i++) {
|
|
359
|
+
let transformRect = originalRects[i].rectWithTransformToRect(sourceRects[i]);
|
|
360
|
+
|
|
361
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(openWindows[i].node, {node: openWindows[i].node});
|
|
362
|
+
controller.registerBuiltInPropertiesWithDictionary({
|
|
363
|
+
translateX: transformRect.origin.x + 'px',
|
|
364
|
+
translateY: transformRect.origin.y + 'px',
|
|
365
|
+
scaleX: transformRect.size.width,
|
|
366
|
+
scaleY: transformRect.size.height,
|
|
367
|
+
translateZ: 0
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}, {duration: 300, easing: 'easeInOutQuad', complete: x => BMWindow.revealAll()});
|
|
371
|
+
|
|
372
|
+
document.body.addEventListener('wheel', BMWindow._wheelHandler, YES);
|
|
373
|
+
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* A mousewheel handler that is attached to the document during showcase mode.
|
|
378
|
+
* @param event <WheelEvent> The event.
|
|
379
|
+
*/
|
|
380
|
+
BMWindow._wheelHandler = function (event) {
|
|
381
|
+
if (event.deltaY < 0) {
|
|
382
|
+
BMWindow.exitShowcase();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Exits showcase mode.
|
|
388
|
+
*/
|
|
389
|
+
BMWindow.exitShowcase = function (event) {
|
|
390
|
+
if (event && event.target != document.body) return;
|
|
391
|
+
|
|
392
|
+
BMWindow._isShowcasing = NO;
|
|
393
|
+
|
|
394
|
+
document.body.removeEventListener('click', BMWindow.exitShowcase);
|
|
395
|
+
document.body.removeEventListener('wheel', BMWindow._wheelHandler, YES);
|
|
396
|
+
|
|
397
|
+
let openWindows = BMWindow._windows.filter(value => !value._minimizedWindow);
|
|
398
|
+
/*let twStudio = document.querySelector('#twStudioBody');
|
|
399
|
+
let pageWindow = {node: twStudio};
|
|
400
|
+
openWindows.unshift(pageWindow);*/
|
|
401
|
+
|
|
402
|
+
for (let element of BMWindow._showcaseElements) {
|
|
403
|
+
openWindows.unshift(element);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
openWindows.forEach(window => {
|
|
407
|
+
window.node.classList.remove('BMWindowShowcaseWindow');
|
|
408
|
+
|
|
409
|
+
let selector = window.node.querySelector('.BMWindowShowcaseWindowSelector');
|
|
410
|
+
if (selector) selector.remove();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
BMAnimateWithBlock(() => {
|
|
414
|
+
BMAnimationContextEnableWebAnimations();
|
|
415
|
+
|
|
416
|
+
/*let header = document.getElementById('twStudioHeader');
|
|
417
|
+
let headerController = BMAnimationContextGetCurrent().controllerForObject(header, {node: header});
|
|
418
|
+
headerController.registerBuiltInProperty('translateY', {withValue: '0px'});*/
|
|
419
|
+
|
|
420
|
+
for (let i = 0; i < openWindows.length; i++) {
|
|
421
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(openWindows[i].node, {node: openWindows[i].node});
|
|
422
|
+
controller.registerBuiltInPropertiesWithDictionary({
|
|
423
|
+
translateX: '0px',
|
|
424
|
+
translateY: '0px',
|
|
425
|
+
scaleX: 1,
|
|
426
|
+
scaleY: 1,
|
|
427
|
+
translateZ: 0
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}, {duration: 300, easing: 'easeInOutQuad', complete: x => openWindows[0].node.style.transform = 'none'});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
BMWindow.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* An optional delegate which this window may notify of key events.
|
|
437
|
+
*/
|
|
438
|
+
delegate: undefined, // <BMWindowDelegate, nullable>
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* The window's frame, which represents its on-screen area.
|
|
442
|
+
*/
|
|
443
|
+
_frame: undefined, // <BMRect>
|
|
444
|
+
|
|
445
|
+
// @override - BMView
|
|
446
|
+
get contentNode() {
|
|
447
|
+
return this.content;
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The view to which subviews should be added.
|
|
452
|
+
*/
|
|
453
|
+
get contentView() { // <BMView>
|
|
454
|
+
return this;
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
get frame() {
|
|
458
|
+
return this._frame;
|
|
459
|
+
},
|
|
460
|
+
set frame(frame) {
|
|
461
|
+
if (this._fullScreen) return;
|
|
462
|
+
|
|
463
|
+
frame = frame.integralRect;
|
|
464
|
+
|
|
465
|
+
this._frame = frame.copy();
|
|
466
|
+
|
|
467
|
+
BMCopyProperties(this._window.style, {
|
|
468
|
+
left: frame.origin.x + 'px',
|
|
469
|
+
top: frame.origin.y + 'px',
|
|
470
|
+
width: frame.size.width + 'px',
|
|
471
|
+
height: frame.size.height + 'px'
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
switch (this.frameHorizontalPositionLayoutAttribute) {
|
|
475
|
+
case BMLayoutAttribute.Left:
|
|
476
|
+
case BMLayoutAttribute.Leading:
|
|
477
|
+
this.leftConstraint.constant = frame.origin.x;
|
|
478
|
+
break;
|
|
479
|
+
case BMLayoutAttribute.CenterX:
|
|
480
|
+
this.leftConstraint.constant = frame.center.x - this.superview.frame.center.x;
|
|
481
|
+
break;
|
|
482
|
+
case BMLayoutAttribute.Right:
|
|
483
|
+
case BMLayoutAttribute.Trailing:
|
|
484
|
+
this.leftConstraint.constant = frame.right - this.superview.frame.right;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
switch (this.frameVerticalPositionLayoutAttribute) {
|
|
489
|
+
case BMLayoutAttribute.Top:
|
|
490
|
+
this.topConstraint.constant = frame.origin.y;
|
|
491
|
+
break;
|
|
492
|
+
case BMLayoutAttribute.CenterY:
|
|
493
|
+
this.topConstraint.constant = frame.center.y - this.superview.frame.center.y;
|
|
494
|
+
break;
|
|
495
|
+
case BMLayoutAttribute.Bottom:
|
|
496
|
+
this.topConstraint.constant = frame.bottom - this.superview.frame.bottom;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
this.widthConstraint.constant = frame.size.width;
|
|
501
|
+
this.heightConstraint.constant = frame.size.height;
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* @protected
|
|
506
|
+
* The priority to use when setting up frame position constraints.
|
|
507
|
+
*/
|
|
508
|
+
get framePositionPriority() { // <Number>
|
|
509
|
+
return 500;
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* @protected
|
|
514
|
+
* The priority to use when setting up frame size constraints.
|
|
515
|
+
*/
|
|
516
|
+
get frameSizePriority() { // <Number>
|
|
517
|
+
return 750;
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* @protected
|
|
522
|
+
* The source anchor point to use when setting up the horizontal position constraint.
|
|
523
|
+
*/
|
|
524
|
+
get frameHorizontalPositionLayoutAttribute() { // <BMLayoutAttribute>
|
|
525
|
+
return BMLayoutAttribute.Left;
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Returns the name of the horizontal anchor from which the horizontal position constraint will be created.
|
|
530
|
+
*/
|
|
531
|
+
get _frameHorizontalAnchorName() { // <String>
|
|
532
|
+
const key = this.frameHorizontalPositionLayoutAttribute;
|
|
533
|
+
return key.substring(0, 1).toLowerCase() + key.substring(1, key.length);
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* @protected
|
|
538
|
+
* The source anchor point to use when setting up the vertical position constraint.
|
|
539
|
+
*/
|
|
540
|
+
get frameVerticalPositionLayoutAttribute() { // <BMLayoutAttribute>
|
|
541
|
+
return BMLayoutAttribute.Top;
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Returns the name of the vertical anchor from which the vertical position constraint will be created.
|
|
546
|
+
*/
|
|
547
|
+
get _frameVerticalAnchorName() { // <String>
|
|
548
|
+
const key = this.frameVerticalPositionLayoutAttribute;
|
|
549
|
+
return key.substring(0, 1).toLowerCase() + key.substring(1, key.length);
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* The window DOM node.
|
|
554
|
+
*/
|
|
555
|
+
_window: undefined, // <DOMNode>
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* The window toolbar DOM node.
|
|
559
|
+
*/
|
|
560
|
+
_toolbar: undefined, // <DOMNode>
|
|
561
|
+
get toolbar() {
|
|
562
|
+
return this._toolbar;
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The window overlay.
|
|
567
|
+
*/
|
|
568
|
+
_overlay: undefined, // <BMWindowOverlay>
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* The modal blocker DOM node.
|
|
572
|
+
*/
|
|
573
|
+
_blocker: undefined, // <DOMNode>
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* The DOM node in which the window contents should be added.
|
|
577
|
+
*/
|
|
578
|
+
_content: undefined, // <DOMNode>
|
|
579
|
+
get content() {
|
|
580
|
+
return this._content;
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* A string that is used for when this window is minimized.
|
|
585
|
+
*/
|
|
586
|
+
_title: 'New Window', // <String>
|
|
587
|
+
get title() {
|
|
588
|
+
return this._title;
|
|
589
|
+
},
|
|
590
|
+
set title(title) {
|
|
591
|
+
this._title = title;
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* The point from which this popover should originate when open or dimissed with an animation.
|
|
596
|
+
* Setting this property causes the `anchorNode` and `anchorRect` properties to be set to `undefined`.
|
|
597
|
+
*/
|
|
598
|
+
_anchorPoint: undefined, // <BMPoint, nullable>
|
|
599
|
+
|
|
600
|
+
get anchorPoint() {
|
|
601
|
+
return this._anchorPoint;
|
|
602
|
+
},
|
|
603
|
+
set anchorPoint(point) {
|
|
604
|
+
this._anchorPoint = point && point.copy();
|
|
605
|
+
this.anchorNode = undefined;
|
|
606
|
+
this._anchorRect = undefined;
|
|
607
|
+
},
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* The rect from which this popover should originate when open or dimissed with an animation.
|
|
611
|
+
* Setting this property causes the `anchorNode` and `anchorPoint` properties to be set to `undefined`.
|
|
612
|
+
*/
|
|
613
|
+
_anchorRect: undefined, // <BMRect, nullable>
|
|
614
|
+
|
|
615
|
+
get anchorRect() {
|
|
616
|
+
return this._anchorRect;
|
|
617
|
+
},
|
|
618
|
+
set anchorRect(rect) {
|
|
619
|
+
this._anchorRect = rect && rect.copy();
|
|
620
|
+
this.anchorNode = undefined;
|
|
621
|
+
this._anchorPoint = undefined;
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* The node from which this popover should originate when open or dimissed with an animation.
|
|
627
|
+
* Setting this property causes the `anchorPoint` and `anchorRect` properties to be set to `undefined`.
|
|
628
|
+
*/
|
|
629
|
+
_anchorNode: undefined, // <DOMNode, nullable>
|
|
630
|
+
|
|
631
|
+
get anchorNode() {
|
|
632
|
+
return this._anchorNode;
|
|
633
|
+
},
|
|
634
|
+
set anchorNode(node) {
|
|
635
|
+
this._anchorNode = node;
|
|
636
|
+
this._anchorPoint = undefined;
|
|
637
|
+
this._anchorRect = undefined;
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Set to `YES` while this window is visible.
|
|
642
|
+
*/
|
|
643
|
+
_visible: NO, // <Boolean>
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Set to `YES` while this window is visible.
|
|
647
|
+
*/
|
|
648
|
+
get isVisible() { // <Boolean>
|
|
649
|
+
return this._visible;
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Set to `YES` while this window is full screen.
|
|
654
|
+
*/
|
|
655
|
+
_fullScreen: NO, // <Boolean>
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Set to `YES` while this window is full screen.
|
|
659
|
+
*/
|
|
660
|
+
get isFullScreen() { // <Boolean>
|
|
661
|
+
return this._fullScreen;
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Set to `YES` if this window is modal.
|
|
666
|
+
*/
|
|
667
|
+
_modal: NO, // <Boolean>
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Set to `YES` if this window is modal.
|
|
671
|
+
*/
|
|
672
|
+
get isModal() {
|
|
673
|
+
return this._modal;
|
|
674
|
+
},
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Set to `YES` if this window is a tool window.
|
|
678
|
+
*/
|
|
679
|
+
_toolWindow: NO, // <Boolean>
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* An array containing the tool windows associated with this window.
|
|
683
|
+
*/
|
|
684
|
+
_toolWindows: undefined, // <[BMToolWindow]>
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* The keyboard shortcuts that have been registered to this window.
|
|
688
|
+
*/
|
|
689
|
+
_keyboardShortcuts: undefined, // <[BMKeyboardShortcut]>
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Initializes this window with the given screen area.
|
|
693
|
+
* @param frame <BMRect> The frame.
|
|
694
|
+
* {
|
|
695
|
+
* @param toolbar <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will have a toolbar.
|
|
696
|
+
* @param modal <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will be modal, blocking content behind it.
|
|
697
|
+
* When set to `NO`, it will be possible to interact with content that is not obstructed by this window.
|
|
698
|
+
* }
|
|
699
|
+
* @return <BMWindow> This window.
|
|
700
|
+
*/
|
|
701
|
+
initWithFrame: function (frame, args) {
|
|
702
|
+
frame = frame.integralRect;
|
|
703
|
+
|
|
704
|
+
let modal = (args && 'modal' in args) ? args.modal : YES;
|
|
705
|
+
|
|
706
|
+
this._toolWindows = [];
|
|
707
|
+
this._keyboardShortcuts = [];
|
|
708
|
+
|
|
709
|
+
this._overlay = (new BMWindowOverlay).initWithWindow(this, {modal});
|
|
710
|
+
this._blocker = this._overlay.node;
|
|
711
|
+
|
|
712
|
+
let node = document.createElement('div');
|
|
713
|
+
this._window = node;
|
|
714
|
+
node.className = 'BMWindow';
|
|
715
|
+
|
|
716
|
+
BMCopyProperties(node.style, {
|
|
717
|
+
opacity: 0,
|
|
718
|
+
display: 'none'
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
this._modal = modal;
|
|
722
|
+
|
|
723
|
+
if (!modal) {
|
|
724
|
+
// BMCopyProperties(node.style, {
|
|
725
|
+
// pointerEvents: 'none',
|
|
726
|
+
// backdropFilter: modal ? 'none' : 'blur(15px)',
|
|
727
|
+
// webkitBackdropFilter: modal ? 'none' : 'blur(15px)'
|
|
728
|
+
// });
|
|
729
|
+
|
|
730
|
+
// In non-modal mode, clicking the window should cause it to become the key window
|
|
731
|
+
if (!this._toolWindow) {
|
|
732
|
+
node.addEventListener('mousedown', event => {
|
|
733
|
+
if (!this.isKeyWindow) this.becomeKeyWindow();
|
|
734
|
+
}, YES);
|
|
735
|
+
|
|
736
|
+
node.classList.add('BMWindowInactive');
|
|
737
|
+
|
|
738
|
+
BMWindow._windows.push(this);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
node.classList.add('BMNonModalWindow');
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
node.classList.add('BMModalWindow');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
this._content = document.createElement('div');
|
|
748
|
+
this._content.className = 'BMWindowContent';
|
|
749
|
+
node.appendChild(this._content);
|
|
750
|
+
|
|
751
|
+
var withToolbar = (!args || args.toolbar === undefined) ? YES : args.toolbar;
|
|
752
|
+
|
|
753
|
+
if (withToolbar) {
|
|
754
|
+
this._toolbar = document.createElement('div');
|
|
755
|
+
this._toolbar.className = 'BMWindowToolbar';
|
|
756
|
+
node.appendChild(this._toolbar);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
this._content.className = 'BMWindowContent BMWindowFullContent';
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
document.body.appendChild(this._blocker);
|
|
763
|
+
BMView.prototype.initWithDOMNode.call(this, node);
|
|
764
|
+
this._overlay.addSubview(this);
|
|
765
|
+
document.body.appendChild(node);
|
|
766
|
+
|
|
767
|
+
this.leftConstraint = this[this._frameHorizontalAnchorName].equalTo(this._overlay[this._frameHorizontalAnchorName], {plus: frame.origin.x, priority: this.framePositionPriority});
|
|
768
|
+
this.topConstraint = this[this._frameVerticalAnchorName].equalTo(this._overlay[this._frameVerticalAnchorName], {plus: frame.origin.y, priority: this.framePositionPriority});
|
|
769
|
+
this.widthConstraint = this.width.equalTo(frame.size.width, {priority: this.frameSizePriority});
|
|
770
|
+
this.heightConstraint = this.height.equalTo(frame.size.height, {priority: this.frameSizePriority});
|
|
771
|
+
this.frame = frame;
|
|
772
|
+
|
|
773
|
+
this.leftConstraint.isActive = YES;
|
|
774
|
+
this.topConstraint.isActive = YES;
|
|
775
|
+
this.widthConstraint.isActive = YES;
|
|
776
|
+
this.heightConstraint.isActive = YES;
|
|
777
|
+
|
|
778
|
+
if (!modal && withToolbar) {
|
|
779
|
+
// In non-modal mode, allow the toolbar to move the window
|
|
780
|
+
this.maxLeftConstraint = this.left.greaterThanOrEqualTo(0);
|
|
781
|
+
this.maxTopConstraint = this.top.greaterThanOrEqualTo(0);
|
|
782
|
+
|
|
783
|
+
this.maxRightConstraint = this.right.lessThanOrEqualTo(window.innerWidth);
|
|
784
|
+
this.maxBottomConstraint = this.bottom.lessThanOrEqualTo(window.innerHeight);
|
|
785
|
+
|
|
786
|
+
this.maxLeftConstraint.isActive = YES;
|
|
787
|
+
this.maxTopConstraint.isActive = YES;
|
|
788
|
+
|
|
789
|
+
this.maxRightConstraint.isActive = YES;
|
|
790
|
+
this.maxBottomConstraint.isActive = YES;
|
|
791
|
+
|
|
792
|
+
window.addEventListener('resize', this._boundViewportDidResize = this._viewportDidResize.bind(this));
|
|
793
|
+
|
|
794
|
+
// Initialize dragging touch events for the toolbar
|
|
795
|
+
this._toolbar.addEventListener('mousedown', event => {
|
|
796
|
+
// Don't process events originating from children of the toolbar
|
|
797
|
+
if (event.target != this._toolbar) return;
|
|
798
|
+
|
|
799
|
+
// Full screen windows cannot be moved
|
|
800
|
+
if (this._fullScreen) return;
|
|
801
|
+
|
|
802
|
+
this._dragged = YES;
|
|
803
|
+
|
|
804
|
+
this._position = BMPointMake(this.node.offsetLeft, this.node.offsetTop);
|
|
805
|
+
let lastPosition = BMPointMake(event.clientX, event.clientY);
|
|
806
|
+
|
|
807
|
+
let mouseMoveEventListener = event => {
|
|
808
|
+
let position = BMPointMake(event.clientX, event.clientY);
|
|
809
|
+
const newPosition = BMPointMake(this._position.x + position.x - lastPosition.x, this._position.y + position.y - lastPosition.y);
|
|
810
|
+
|
|
811
|
+
// Check if the window can move
|
|
812
|
+
let canMove = YES;
|
|
813
|
+
if (this.delegate && this.delegate.windowShouldMove) {
|
|
814
|
+
canMove = this.delegate.windowShouldMove(this, newPosition);
|
|
815
|
+
}
|
|
816
|
+
if (!canMove) return;
|
|
817
|
+
|
|
818
|
+
this.leftConstraint.constant = this._position.x + position.x - lastPosition.x;
|
|
819
|
+
this.topConstraint.constant = this._position.y + position.y - lastPosition.y;
|
|
820
|
+
this._position = newPosition;
|
|
821
|
+
lastPosition = position;
|
|
822
|
+
this.layout();
|
|
823
|
+
event.preventDefault();
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
let mouseUpEventListener = event => {
|
|
827
|
+
window.removeEventListener('mousemove', mouseMoveEventListener, YES);
|
|
828
|
+
window.removeEventListener('mouseup', mouseUpEventListener, YES);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
window.addEventListener('mousemove', mouseMoveEventListener, YES);
|
|
832
|
+
window.addEventListener('mouseup', mouseUpEventListener, YES);
|
|
833
|
+
|
|
834
|
+
event.preventDefault();
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
let touchDragPoint;
|
|
838
|
+
|
|
839
|
+
this._toolbar.addEventListener('touchstart', /** @type {TouchEvent} */ event => {
|
|
840
|
+
// If there is already a drag in progress, don't process this new event
|
|
841
|
+
if (typeof touchDragPoint !== 'undefined') {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Don't process events originating from children of the toolbar
|
|
846
|
+
if (event.target != this._toolbar) return;
|
|
847
|
+
|
|
848
|
+
// Full screen windows cannot be moved
|
|
849
|
+
if (this._fullScreen) return;
|
|
850
|
+
|
|
851
|
+
// Only use the first touch point
|
|
852
|
+
touchDragPoint = event.changedTouches[0].identifier;
|
|
853
|
+
this._dragged = YES;
|
|
854
|
+
|
|
855
|
+
this._position = BMPointMake(this.node.offsetLeft, this.node.offsetTop);
|
|
856
|
+
let lastPosition = BMPointMake(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
|
|
857
|
+
|
|
858
|
+
let mouseMoveEventListener = event => {
|
|
859
|
+
// Look for the actively tracked touch point
|
|
860
|
+
let touch;
|
|
861
|
+
for (let changedTouch of event.changedTouches) {
|
|
862
|
+
if (changedTouch.identifier == touchDragPoint) {
|
|
863
|
+
touch = changedTouch;
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// If the actively tracked touch point did not move, do not process this event
|
|
869
|
+
if (!touch) return;
|
|
870
|
+
|
|
871
|
+
let position = BMPointMake(touch.clientX, touch.clientY);
|
|
872
|
+
const newPosition = BMPointMake(this._position.x + position.x - lastPosition.x, this._position.y + position.y - lastPosition.y);
|
|
873
|
+
|
|
874
|
+
// Check if the window can move
|
|
875
|
+
let canMove = YES;
|
|
876
|
+
if (this.delegate && this.delegate.windowShouldMove) {
|
|
877
|
+
canMove = this.delegate.windowShouldMove(this, newPosition);
|
|
878
|
+
}
|
|
879
|
+
if (!canMove) return;
|
|
880
|
+
|
|
881
|
+
this.leftConstraint.constant = this._position.x + position.x - lastPosition.x;
|
|
882
|
+
this.topConstraint.constant = this._position.y + position.y - lastPosition.y;
|
|
883
|
+
this._position = newPosition;
|
|
884
|
+
lastPosition = position;
|
|
885
|
+
this.layout();
|
|
886
|
+
event.preventDefault();
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
let mouseUpEventListener = event => {
|
|
890
|
+
touchDragPoint = undefined;
|
|
891
|
+
window.removeEventListener('touchmove', mouseMoveEventListener);
|
|
892
|
+
window.removeEventListener('touchend', mouseUpEventListener);
|
|
893
|
+
window.removeEventListener('touchcancel', mouseUpEventListener);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
window.addEventListener('touchmove', mouseMoveEventListener);
|
|
897
|
+
window.addEventListener('touchend', mouseUpEventListener);
|
|
898
|
+
window.removeEventListener('touchcancel', mouseUpEventListener);
|
|
899
|
+
|
|
900
|
+
event.preventDefault();
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
this._toolbar.addEventListener('dbclick', event => {
|
|
904
|
+
this.minimizeAnimated(YES);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
this._toolbar.addEventListener('wheel', /** @param {WheelEvent} event */ event => {
|
|
908
|
+
// Scrolling on the toolbar should enter showcase
|
|
909
|
+
if (event.deltaY > 0) {
|
|
910
|
+
// Allow the delegate to suppress this behaviour
|
|
911
|
+
let shouldEnterShowcase = YES;
|
|
912
|
+
if (this.delegate && this.delegate.windowShouldEnterShowcase) {
|
|
913
|
+
shouldEnterShowcase = this.delegate.windowShouldEnterShowcase(this);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (shouldEnterShowcase) {
|
|
917
|
+
BMWindow.enterShowcase();
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
// Also create a drag handle to resize the window
|
|
923
|
+
let dragHandle = this._dragHandle = document.createElement('div');
|
|
924
|
+
dragHandle.className = 'material-icons BMWindowDragHandle';
|
|
925
|
+
dragHandle.innerText = 'dehaze';
|
|
926
|
+
dragHandle.style.cursor = 'nwse-resize';
|
|
927
|
+
node.appendChild(dragHandle);
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
// Initialize dragging touch events for the drag handler
|
|
931
|
+
dragHandle.addEventListener('mousedown', event => {
|
|
932
|
+
|
|
933
|
+
// Full screen windows cannot be resized
|
|
934
|
+
if (this._fullScreen) return;
|
|
935
|
+
|
|
936
|
+
this._dragged = YES;
|
|
937
|
+
|
|
938
|
+
this._position = BMPointMake(this.widthConstraint.constant, this.heightConstraint.constant);
|
|
939
|
+
let lastPosition = BMPointMake(event.clientX, event.clientY);
|
|
940
|
+
|
|
941
|
+
let mouseMoveEventListener = event => {
|
|
942
|
+
let position = BMPointMake(event.clientX, event.clientY);
|
|
943
|
+
|
|
944
|
+
const newWidth = this._position.x + position.x - lastPosition.x;
|
|
945
|
+
const newHeight = this._position.y + position.y - lastPosition.y;
|
|
946
|
+
|
|
947
|
+
this._position = BMPointMake(newWidth, newHeight);
|
|
948
|
+
lastPosition = position;
|
|
949
|
+
|
|
950
|
+
let canResize = YES;
|
|
951
|
+
if (this.delegate && this.delegate.windowShouldResize) {
|
|
952
|
+
canResize = this.delegate.windowShouldResize(this, BMSizeMake(newWidth, newHeight));
|
|
953
|
+
}
|
|
954
|
+
if (!canResize) return;
|
|
955
|
+
|
|
956
|
+
this.widthConstraint.constant = newWidth;
|
|
957
|
+
this.heightConstraint.constant = newHeight;
|
|
958
|
+
this.frame.size.width = this.widthConstraint.constant;
|
|
959
|
+
this.frame.size.height = this.heightConstraint.constant;
|
|
960
|
+
this.layout();
|
|
961
|
+
if (this.delegate && this.delegate.windowDidResize) {
|
|
962
|
+
this.delegate.windowDidResize(this, {toSize: this.frame.size});
|
|
963
|
+
}
|
|
964
|
+
event.preventDefault();
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
let mask = document.createElement('div');
|
|
968
|
+
mask.className = 'BMLayoutGuideMask';
|
|
969
|
+
mask.style.cursor = 'nwse-resize';
|
|
970
|
+
document.body.appendChild(mask);
|
|
971
|
+
|
|
972
|
+
let mouseUpEventListener = event => {
|
|
973
|
+
window.removeEventListener('mousemove', mouseMoveEventListener, YES);
|
|
974
|
+
window.removeEventListener('mouseup', mouseUpEventListener, YES);
|
|
975
|
+
mask.remove();
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
window.addEventListener('mousemove', mouseMoveEventListener, YES);
|
|
979
|
+
window.addEventListener('mouseup', mouseUpEventListener, YES);
|
|
980
|
+
|
|
981
|
+
event.preventDefault();
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
let touchDragHandlePoint;
|
|
985
|
+
|
|
986
|
+
dragHandle.addEventListener('touchstart', /** @type {TouchEvent} */ event => {
|
|
987
|
+
// Full screen windows cannot be resized
|
|
988
|
+
if (this._fullScreen) return;
|
|
989
|
+
|
|
990
|
+
// If there is already a drag in progress, don't process this new event
|
|
991
|
+
if (typeof touchDragPoint !== 'undefined') {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Only use the first touch point
|
|
996
|
+
touchDragHandlePoint = event.changedTouches[0].identifier;
|
|
997
|
+
this._dragged = YES;
|
|
998
|
+
|
|
999
|
+
this._position = BMPointMake(this.widthConstraint.constant, this.heightConstraint.constant);
|
|
1000
|
+
let lastPosition = BMPointMake(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
|
|
1001
|
+
|
|
1002
|
+
let mouseMoveEventListener = event => {
|
|
1003
|
+
// Look for the actively tracked touch point
|
|
1004
|
+
let touch;
|
|
1005
|
+
for (let changedTouch of event.changedTouches) {
|
|
1006
|
+
if (changedTouch.identifier == touchDragHandlePoint) {
|
|
1007
|
+
touch = changedTouch;
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// If the actively tracked touch point did not move, do not process this event
|
|
1013
|
+
if (!touch) return;
|
|
1014
|
+
|
|
1015
|
+
let position = BMPointMake(touch.clientX, touch.clientY);
|
|
1016
|
+
|
|
1017
|
+
const newWidth = this._position.x + position.x - lastPosition.x;
|
|
1018
|
+
const newHeight = this._position.y + position.y - lastPosition.y;
|
|
1019
|
+
|
|
1020
|
+
this._position = BMPointMake(newWidth, newHeight);
|
|
1021
|
+
lastPosition = position;
|
|
1022
|
+
|
|
1023
|
+
let canResize = YES;
|
|
1024
|
+
if (this.delegate && this.delegate.windowShouldResize) {
|
|
1025
|
+
canResize = this.delegate.windowShouldResize(this, BMSizeMake(newWidth, newHeight));
|
|
1026
|
+
}
|
|
1027
|
+
if (!canResize) return;
|
|
1028
|
+
|
|
1029
|
+
this.widthConstraint.constant = newWidth;
|
|
1030
|
+
this.heightConstraint.constant = newHeight;
|
|
1031
|
+
this.frame.size.width = this.widthConstraint.constant;
|
|
1032
|
+
this.frame.size.height = this.heightConstraint.constant;
|
|
1033
|
+
|
|
1034
|
+
this.layout();
|
|
1035
|
+
if (this.delegate && this.delegate.windowDidResize) {
|
|
1036
|
+
this.delegate.windowDidResize(this, {toSize: this.frame.size});
|
|
1037
|
+
}
|
|
1038
|
+
event.preventDefault();
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
let mouseUpEventListener = event => {
|
|
1042
|
+
touchDragHandlePoint = undefined;
|
|
1043
|
+
window.removeEventListener('touchmove', mouseMoveEventListener);
|
|
1044
|
+
window.removeEventListener('touchend', mouseUpEventListener);
|
|
1045
|
+
window.removeEventListener('touchcancel', mouseUpEventListener);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
window.addEventListener('touchmove', mouseMoveEventListener);
|
|
1049
|
+
window.addEventListener('touchend', mouseUpEventListener);
|
|
1050
|
+
window.removeEventListener('touchcancel', mouseUpEventListener);
|
|
1051
|
+
|
|
1052
|
+
event.preventDefault();
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return this;
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Invoked on non-modal windows when the viewport resizes.
|
|
1061
|
+
* @param event <Event> The event that triggered this action.
|
|
1062
|
+
*/
|
|
1063
|
+
_viewportDidResize(event) {
|
|
1064
|
+
this.maxRightConstraint.constant = window.innerWidth;
|
|
1065
|
+
this.maxBottomConstraint.constant = window.innerHeight;
|
|
1066
|
+
|
|
1067
|
+
if (this._fullScreen) this.layout();
|
|
1068
|
+
},
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Registers a keyboard shortcut that can be activated when this window is the key window.
|
|
1072
|
+
* @param shortcut <BMKeyboardShortcut> The keyboard shortcut to register.
|
|
1073
|
+
*/
|
|
1074
|
+
registerKeyboardShortcut(shortcut) {
|
|
1075
|
+
if (!this._keyboardShortcuts.length) this._enableKeyboardShortcuts();
|
|
1076
|
+
|
|
1077
|
+
this._keyboardShortcuts.push(shortcut);
|
|
1078
|
+
},
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Unregisters a keyboard shortcut. If this keyboard shortcut had not been previously registered, this method does nothing.
|
|
1082
|
+
* @param shortcut <BMKeyboardShortcut> The keyboard shortcut to unregister.
|
|
1083
|
+
*/
|
|
1084
|
+
unregisterKeyboardShortcut(shortcut) {
|
|
1085
|
+
for (let i = 0; i < this._keyboardShortcuts.length; i++) {
|
|
1086
|
+
if (this._keyboardShortcuts[i] == shortcut) {
|
|
1087
|
+
this._keyboardShortcuts.splice(i, 1);
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (!this._keyboardShortcuts.length) this._disableKeyboardShortcuts();
|
|
1093
|
+
},
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* If keyboard shortcuts have been registered on this window, this method will be invoked to identify
|
|
1097
|
+
* and handle key presses. Subclasses that override this method should invoke the base implementation to allow
|
|
1098
|
+
* keyboard shortcuts to be handled correctly.
|
|
1099
|
+
* @param event <KeyboardEvent> The event that triggered this action.
|
|
1100
|
+
*/
|
|
1101
|
+
keyPressedWithEvent(event) {
|
|
1102
|
+
// Check if a shortcut key has been pressed.
|
|
1103
|
+
for (const shortcut of this._keyboardShortcuts) {
|
|
1104
|
+
if (event.code != shortcut.keyCode) continue;
|
|
1105
|
+
|
|
1106
|
+
// Build the modifier bitmap for this event
|
|
1107
|
+
let bitmap = 0;
|
|
1108
|
+
for (const modifier in BMKeyboardShortcutModifier) {
|
|
1109
|
+
if (event[BMKeyboardShortcutModifier[modifier].key]) {
|
|
1110
|
+
bitmap = bitmap | BMKeyboardShortcutModifier[modifier].value;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (bitmap == shortcut._modifierBitmap) {
|
|
1115
|
+
if (shortcut.preventsDefault) event.preventDefault();
|
|
1116
|
+
shortcut.target[shortcut.action](event);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Set to `YES` if keyboard shortcuts are enabled.
|
|
1123
|
+
*/
|
|
1124
|
+
_keyboardShortcutsEnabled: NO, // <Boolean>
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Enables keyboard shortcut handling. This makes the window's `node` focusable and attaches
|
|
1128
|
+
* the relevant event handlers to it. If this window is the key window and the keyboard focus
|
|
1129
|
+
* is outside of this window, this window will acquire keyboard focus.
|
|
1130
|
+
*/
|
|
1131
|
+
_enableKeyboardShortcuts() {
|
|
1132
|
+
this.node.tabIndex = -1;
|
|
1133
|
+
|
|
1134
|
+
if (document.activeElement && !this.node.contains(document.activeElement)) {
|
|
1135
|
+
this.node.focus();
|
|
1136
|
+
}
|
|
1137
|
+
else if (!document.activeElement) {
|
|
1138
|
+
this.node.focus();
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
this._keypressHandler = (event) => {
|
|
1142
|
+
this.keyPressedWithEvent(event);
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
this.node.addEventListener('keydown', this._keypressHandler);
|
|
1146
|
+
this._keyboardShortcutsEnabled = YES;
|
|
1147
|
+
},
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Disables keyboard shortcut handling. This detaches the relevant event handlers from this window's `node`.
|
|
1151
|
+
*/
|
|
1152
|
+
_disableKeyboardShortcuts() {
|
|
1153
|
+
this.node.removeEventListener('keydown', this._keypressHandler);
|
|
1154
|
+
this._keyboardShortcutsEnabled = NO;
|
|
1155
|
+
},
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* If this window is visible, this method dismisses it. Otherwise the window is made visible.
|
|
1159
|
+
* @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated, otherwise it will be instant.
|
|
1160
|
+
* {
|
|
1161
|
+
* @param completionHandler <void ^ (), nullable> If specified, this handler will be invoked when the operation completes.
|
|
1162
|
+
* }
|
|
1163
|
+
*/
|
|
1164
|
+
toggleAnimated: function (animated, args) {
|
|
1165
|
+
if (this._visible) {
|
|
1166
|
+
this.dismissAnimated(animated, args);
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
this.bringToFrontAnimated(animated, args);
|
|
1170
|
+
}
|
|
1171
|
+
},
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* @protected
|
|
1175
|
+
* This method is invoked during `bringToFrontAnimated()` to run the animations that will make this window visible.
|
|
1176
|
+
* Subclasses can override this method to perform custom animations.
|
|
1177
|
+
*
|
|
1178
|
+
* This method is not invoked from within an animation context.
|
|
1179
|
+
* @param completionHandler <void ^()> A method that custom implementations must invoke when the animation completes.
|
|
1180
|
+
*/
|
|
1181
|
+
animateInWithCompletionHandler(completionHandler) {
|
|
1182
|
+
const self = this;
|
|
1183
|
+
if (this._anchorRect) {
|
|
1184
|
+
const rect = this._anchorRect;
|
|
1185
|
+
const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
|
|
1186
|
+
const transformRect = frame.rectWithTransformToRect(rect);
|
|
1187
|
+
|
|
1188
|
+
__BMVelocityAnimate(this._window, {opacity: 1, translateX: ['0px', transformRect.origin.x + 'px'], translateY: ['0px', transformRect.origin.y + 'px'],
|
|
1189
|
+
scaleX: [1, transformRect.size.width], scaleY: [1, transformRect.size.height]}, {
|
|
1190
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1191
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1192
|
+
display: 'block'
|
|
1193
|
+
}, YES);
|
|
1194
|
+
}
|
|
1195
|
+
else if (this._anchorNode) {
|
|
1196
|
+
// Create a copy of the given node which will be used in the animation in place of the original node
|
|
1197
|
+
const node = this._anchorNode;
|
|
1198
|
+
const rect = BMRectMakeWithNodeFrame(node);
|
|
1199
|
+
const animationNode = node.cloneNode(YES);
|
|
1200
|
+
|
|
1201
|
+
const nodeDisplay = node.style.display;
|
|
1202
|
+
|
|
1203
|
+
animationNode.style.position = 'fixed';
|
|
1204
|
+
animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
|
|
1205
|
+
animationNode.style.transform = '';
|
|
1206
|
+
BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
|
|
1207
|
+
|
|
1208
|
+
// Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
|
|
1209
|
+
animationNode.style.pointerEvents = 'none';
|
|
1210
|
+
|
|
1211
|
+
// Compute the transform and apply it first to the window
|
|
1212
|
+
// and then to the animation node, but inversed
|
|
1213
|
+
const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
|
|
1214
|
+
const transformRect = frame.rectWithTransformToRect(rect);
|
|
1215
|
+
|
|
1216
|
+
document.body.appendChild(animationNode);
|
|
1217
|
+
node.style.display = 'none';
|
|
1218
|
+
|
|
1219
|
+
__BMVelocityAnimate(this._window, {opacity: 1, translateX: ['0px', transformRect.origin.x + 'px'], translateY: ['0px', transformRect.origin.y + 'px'],
|
|
1220
|
+
scaleX: [1, transformRect.size.width], scaleY: [1, transformRect.size.height]}, {
|
|
1221
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1222
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1223
|
+
display: 'block',
|
|
1224
|
+
complete: completionHandler
|
|
1225
|
+
}, YES);
|
|
1226
|
+
|
|
1227
|
+
// After finishing the animation, remove the temporary node and restore the original node's display
|
|
1228
|
+
__BMVelocityAnimate(animationNode, {opacity: [0, 1], translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
|
|
1229
|
+
scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height}, {
|
|
1230
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1231
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1232
|
+
complete: function () {
|
|
1233
|
+
animationNode.remove();
|
|
1234
|
+
var windowShouldKeepNodeHidden = NO;
|
|
1235
|
+
if (self.delegate && self.delegate.windowShouldKeepNodeHidden) {
|
|
1236
|
+
windowShouldKeepNodeHidden = self.delegate.windowShouldKeepNodeHidden(self, node);
|
|
1237
|
+
}
|
|
1238
|
+
if (windowShouldKeepNodeHidden) {
|
|
1239
|
+
self._nodeDisplay = nodeDisplay;
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
node.style.display = nodeDisplay;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
completionHandler();
|
|
1246
|
+
}
|
|
1247
|
+
}, YES);
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
|
|
1251
|
+
__BMVelocityAnimate(this._window, {opacity: 1, scaleX: [1, .9], scaleY: [1, .9], translateZ: 0}, {
|
|
1252
|
+
duration: _BMWindowAnimationDurationIncomingDefault,
|
|
1253
|
+
easing: _BMWindowAnimationEasingIncomingDefault,
|
|
1254
|
+
display: 'block',
|
|
1255
|
+
complete: completionHandler
|
|
1256
|
+
}, YES);
|
|
1257
|
+
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Should be invoked to make this window visible. Subclasses overriding this method should invoke the base method at some point in their implementation.
|
|
1263
|
+
* @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
|
|
1264
|
+
* {
|
|
1265
|
+
* @param fromRect <BMRect, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
|
|
1266
|
+
* Sets the value of the `anchorRect` property to the given value until this operation completes.
|
|
1267
|
+
* @param fromNode <DOMNode, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
|
|
1268
|
+
* Sets the value of the `anchorNode` property to the given value until this operation completes.
|
|
1269
|
+
* @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been made visible.
|
|
1270
|
+
* }
|
|
1271
|
+
*/
|
|
1272
|
+
bringToFrontAnimated: function (animated, args = {}) {
|
|
1273
|
+
this._visible = YES;
|
|
1274
|
+
|
|
1275
|
+
this.becomeKeyWindow();
|
|
1276
|
+
|
|
1277
|
+
for (let window of this._toolWindows) {
|
|
1278
|
+
if (window.opensAutomatically) window.bringToFrontAnimated(animated);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
animated = (animated === undefined ? YES : animated);
|
|
1282
|
+
|
|
1283
|
+
//__BMVelocityAnimate(this._blocker, 'stop');
|
|
1284
|
+
//__BMVelocityAnimate(this._window, 'stop');
|
|
1285
|
+
|
|
1286
|
+
var self = this;
|
|
1287
|
+
|
|
1288
|
+
if (self.delegate && self.delegate.windowWillAppear) {
|
|
1289
|
+
self.delegate.windowWillAppear(self);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (animated) {
|
|
1293
|
+
|
|
1294
|
+
this._blocker.style.pointerEvents = 'none';
|
|
1295
|
+
this._window.style.pointerEvents = 'none';
|
|
1296
|
+
|
|
1297
|
+
__BMVelocityAnimate(this._blocker, {opacity: 1, translateZ: 0}, {
|
|
1298
|
+
duration: 500,
|
|
1299
|
+
easing: 'easeInOutQuint',
|
|
1300
|
+
display: 'block',
|
|
1301
|
+
/*progress: function (elements, complete) {
|
|
1302
|
+
self._blocker.style.webkitBackdropFilter = 'blur(' + (complete * 15).toFixed(2) + 'px)';
|
|
1303
|
+
self._blocker.style.backdropFilter = 'blur(' + (complete * 15).toFixed(2) + 'px)';
|
|
1304
|
+
},*/
|
|
1305
|
+
complete: function () {
|
|
1306
|
+
self._blocker.style.pointerEvents = '';
|
|
1307
|
+
self._window.style.pointerEvents = '';
|
|
1308
|
+
|
|
1309
|
+
if (self.delegate && self.delegate.windowDidAppear) {
|
|
1310
|
+
self.delegate.windowDidAppear(self);
|
|
1311
|
+
}
|
|
1312
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1313
|
+
}
|
|
1314
|
+
}, YES);
|
|
1315
|
+
|
|
1316
|
+
// Included for compatibility with the anchor arguments,
|
|
1317
|
+
// due to be removed soon
|
|
1318
|
+
let completionHandler;
|
|
1319
|
+
if (this._anchorNode || this._anchorRect || this._anchorPoint) {
|
|
1320
|
+
completionHandler = () => {};
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
if (args.fromRect) {
|
|
1324
|
+
this.anchorRect = args.fromRect;
|
|
1325
|
+
}
|
|
1326
|
+
if (args.fromNode) {
|
|
1327
|
+
this.anchorNode = args.fromNode;
|
|
1328
|
+
}
|
|
1329
|
+
completionHandler = () => {
|
|
1330
|
+
this.anchorRect = undefined;
|
|
1331
|
+
this.anchorNode = undefined;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
this.animateInWithCompletionHandler(completionHandler);
|
|
1336
|
+
|
|
1337
|
+
this._blocker.style.display = 'block';
|
|
1338
|
+
this._window.style.display = 'block';
|
|
1339
|
+
|
|
1340
|
+
this._overlay._windowWillAppear();
|
|
1341
|
+
|
|
1342
|
+
}
|
|
1343
|
+
else {
|
|
1344
|
+
this._blocker.style.opacity = 1;
|
|
1345
|
+
this._blocker.style.display = 'block';
|
|
1346
|
+
|
|
1347
|
+
this._window.style.opacity = 1;
|
|
1348
|
+
BMHook(this._window, {scaleX: 1, scaleY: 1});
|
|
1349
|
+
this._window.style.display = 'block';
|
|
1350
|
+
|
|
1351
|
+
this._overlay._windowWillAppear();
|
|
1352
|
+
|
|
1353
|
+
this._blocker.style.pointerEvents = '';
|
|
1354
|
+
this._window.style.pointerEvents = '';
|
|
1355
|
+
|
|
1356
|
+
if (self.delegate && self.delegate.windowDidAppear) self.delegate.windowDidAppear(self);
|
|
1357
|
+
|
|
1358
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (this._keyboardShortcutsEnabled) {
|
|
1362
|
+
this.node.focus();
|
|
1363
|
+
}
|
|
1364
|
+
},
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* @protected
|
|
1370
|
+
* This method is invoked during `dismissAnimated()` to run the animations that will make this window hidden.
|
|
1371
|
+
* Subclasses can override this method to perform custom animations.
|
|
1372
|
+
*
|
|
1373
|
+
* This method is not invoked from within an animation context.
|
|
1374
|
+
* @param completionHandler <void ^()> A method that custom implementations must invoke when the animation completes.
|
|
1375
|
+
*/
|
|
1376
|
+
animateOutWithCompletionHandler(completionHandler) {
|
|
1377
|
+
const self = this;
|
|
1378
|
+
|
|
1379
|
+
if (this._anchorRect) {
|
|
1380
|
+
const rect = this._anchorRect;
|
|
1381
|
+
const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
|
|
1382
|
+
const transformRect = frame.rectWithTransformToRect(rect);
|
|
1383
|
+
|
|
1384
|
+
__BMVelocityAnimate(this._window, {opacity: 0, translateX: transformRect.origin.x + 'px', translateY: transformRect.origin.y + 'px',
|
|
1385
|
+
scaleX: transformRect.size.width, scaleY: transformRect.size.height}, {
|
|
1386
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1387
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1388
|
+
display: 'none',
|
|
1389
|
+
complete: completionHandler
|
|
1390
|
+
}, YES);
|
|
1391
|
+
}
|
|
1392
|
+
else if (this._anchorNode) {
|
|
1393
|
+
// Create a copy of the given node which will be used in the animation in place of the original node
|
|
1394
|
+
const node = this._anchorNode;
|
|
1395
|
+
|
|
1396
|
+
if ('_nodeDisplay' in self) {
|
|
1397
|
+
node.style.display = self._nodeDisplay;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
const rect = BMRectMakeWithNodeFrame(node);
|
|
1401
|
+
const animationNode = node.cloneNode(YES);
|
|
1402
|
+
|
|
1403
|
+
const nodeDisplay = ('_nodeDisplay' in self) ? self._nodeDisplay : node.style.display;
|
|
1404
|
+
self._nodeDisplay = undefined;
|
|
1405
|
+
|
|
1406
|
+
animationNode.style.position = 'fixed';
|
|
1407
|
+
animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
|
|
1408
|
+
animationNode.style.transform = '';
|
|
1409
|
+
BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
|
|
1410
|
+
|
|
1411
|
+
// Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
|
|
1412
|
+
animationNode.style.pointerEvents = 'none';
|
|
1413
|
+
|
|
1414
|
+
// Compute the transform and apply it first to the window
|
|
1415
|
+
// and then to the animation node, but inversed
|
|
1416
|
+
const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
|
|
1417
|
+
const transformRect = frame.rectWithTransformToRect(rect);
|
|
1418
|
+
|
|
1419
|
+
BMHook(animationNode, {translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
|
|
1420
|
+
scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height, opacity: 0});
|
|
1421
|
+
|
|
1422
|
+
document.body.appendChild(animationNode);
|
|
1423
|
+
node.style.display = 'none';
|
|
1424
|
+
|
|
1425
|
+
__BMVelocityAnimate(this._window, {opacity: 0, translateX: transformRect.origin.x + 'px', translateY: transformRect.origin.y + 'px',
|
|
1426
|
+
scaleX: transformRect.size.width, scaleY: transformRect.size.height}, {
|
|
1427
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1428
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1429
|
+
display: 'none',
|
|
1430
|
+
complete: completionHandler
|
|
1431
|
+
}, YES);
|
|
1432
|
+
|
|
1433
|
+
// After finishing the animation, remove the temporary node and restore the original node's display
|
|
1434
|
+
__BMVelocityAnimate(animationNode, {opacity: [1, 0], translateX: ['0px', -transformRect.origin.x + 'px'], translateY: ['0px', -transformRect.origin.y + 'px'],
|
|
1435
|
+
scaleX: [1, 1 / transformRect.size.width], scaleY: [1, 1 / transformRect.size.height]}, {
|
|
1436
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1437
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1438
|
+
complete: function () { node.style.display = nodeDisplay; animationNode.remove(); }
|
|
1439
|
+
}, YES);
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
__BMVelocityAnimate(this._window, {opacity: 0, scaleX: .9, scaleY: .9, translateZ: 0}, {
|
|
1443
|
+
duration: _BMWindowAnimationDurationOutgoingDefault,
|
|
1444
|
+
easing: _BMWindowAnimationEasingOutgoingDefault,
|
|
1445
|
+
display: 'none',
|
|
1446
|
+
complete: completionHandler
|
|
1447
|
+
}, YES);
|
|
1448
|
+
}
|
|
1449
|
+
},
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Should be invoked to dismiss this window. Subclasses overriding this method should invoke the base method at some point in their implementation.
|
|
1453
|
+
* @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
|
|
1454
|
+
* {
|
|
1455
|
+
* @param toRect <BMRect, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
|
|
1456
|
+
* Sets the value of the `anchorRect` property to the given value until this operation completes.
|
|
1457
|
+
* @param toNode <DOMNode, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
|
|
1458
|
+
* Sets the value of the `anchorNode` property to the given value until this operation completes.
|
|
1459
|
+
* If set to a DOM node, the window will compact to that element.
|
|
1460
|
+
* @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been hidden.
|
|
1461
|
+
* }
|
|
1462
|
+
*/
|
|
1463
|
+
dismissAnimated: function (animated, args = {}) {
|
|
1464
|
+
|
|
1465
|
+
animated = (animated === undefined ? YES : animated);
|
|
1466
|
+
|
|
1467
|
+
//__BMVelocityAnimate(this._blocker, 'stop');
|
|
1468
|
+
//__BMVelocityAnimate(this._window, 'stop');
|
|
1469
|
+
|
|
1470
|
+
var self = this;
|
|
1471
|
+
|
|
1472
|
+
this._blocker.style.pointerEvents = 'none';
|
|
1473
|
+
this._window.style.pointerEvents = 'none';
|
|
1474
|
+
|
|
1475
|
+
if (self.delegate && self.delegate.windowWillClose) {
|
|
1476
|
+
self.delegate.windowWillClose(self);
|
|
1477
|
+
}
|
|
1478
|
+
this._visible = NO;
|
|
1479
|
+
|
|
1480
|
+
for (let window of this._toolWindows) {
|
|
1481
|
+
window.dismissAnimated(animated);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if (animated) {
|
|
1485
|
+
|
|
1486
|
+
__BMVelocityAnimate(this._blocker, {opacity: 0, translateZ: 0}, {
|
|
1487
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1488
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1489
|
+
display: 'none',
|
|
1490
|
+
/*progress: function (elements, complete) {
|
|
1491
|
+
self._blocker.style.webkitBackdropFilter = 'blur(' + ((1 - complete) * 15).toFixed(2) + 'px)';
|
|
1492
|
+
self._blocker.style.backdropFilter = 'blur(' + ((1 - complete) * 15).toFixed(2) + 'px)';
|
|
1493
|
+
},*/
|
|
1494
|
+
complete: function () {
|
|
1495
|
+
if (self.delegate && self.delegate.windowDidClose) {
|
|
1496
|
+
self.delegate.windowDidClose(self);
|
|
1497
|
+
}
|
|
1498
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1499
|
+
}
|
|
1500
|
+
}, YES);
|
|
1501
|
+
|
|
1502
|
+
// Included for compatibility with the anchor arguments,
|
|
1503
|
+
// due to be removed soon
|
|
1504
|
+
let completionHandler;
|
|
1505
|
+
if (this._anchorNode || this._anchorRect || this._anchorPoint) {
|
|
1506
|
+
completionHandler = () => {
|
|
1507
|
+
this._window.style.display = 'none';
|
|
1508
|
+
this._window.style.opacity = 0;
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
else {
|
|
1512
|
+
if (args.fromRect) {
|
|
1513
|
+
this.anchorRect = args.fromRect;
|
|
1514
|
+
}
|
|
1515
|
+
if (args.fromNode) {
|
|
1516
|
+
this.anchorNode = args.fromNode;
|
|
1517
|
+
}
|
|
1518
|
+
completionHandler = () => {
|
|
1519
|
+
this.anchorRect = undefined;
|
|
1520
|
+
this.anchorNode = undefined;
|
|
1521
|
+
this._window.style.display = 'none';
|
|
1522
|
+
this._window.style.opacity = 0;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
this.animateOutWithCompletionHandler(completionHandler);
|
|
1527
|
+
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
this._blocker.style.opacity = 0;
|
|
1531
|
+
this._blocker.style.display = 'none';
|
|
1532
|
+
|
|
1533
|
+
this._window.style.opacity = 0;
|
|
1534
|
+
this._window.style.display = 'none';
|
|
1535
|
+
|
|
1536
|
+
if (self.delegate && self.delegate.windowDidClose) {
|
|
1537
|
+
self.delegate.windowDidClose(self);
|
|
1538
|
+
}
|
|
1539
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* Minimizes this window. This method will raise an error if this window is modal.
|
|
1545
|
+
* If this window is already minimized, this method will have no effect.
|
|
1546
|
+
* @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated, otherwise it will be instant.
|
|
1547
|
+
* Setting this value to `NO` is currently unsupported.
|
|
1548
|
+
* {
|
|
1549
|
+
* @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been minimized.
|
|
1550
|
+
* }
|
|
1551
|
+
*/
|
|
1552
|
+
minimizeAnimated: function (animated = YES, args = {}) {
|
|
1553
|
+
if (this._modal) throw new Error('A modal window cannot be minimized.');
|
|
1554
|
+
if (this._minimizedWindow) return;
|
|
1555
|
+
|
|
1556
|
+
if (this.delegate && this.delegate.windowWillMinimize) {
|
|
1557
|
+
this.delegate.windowWillMinimize(this);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
let minimizedWindow = document.createElement('div');
|
|
1561
|
+
minimizedWindow.className = 'BMWindowMinimized';
|
|
1562
|
+
minimizedWindow.innerText = this.title;
|
|
1563
|
+
minimizedWindow.style.left = 2 + BMWindow._minimizedWindows.length * 258 + 'px';
|
|
1564
|
+
minimizedWindow.addEventListener('click', event => {
|
|
1565
|
+
if (event.altKey) {
|
|
1566
|
+
BMWindow.restoreAllAnimated(YES);
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
this.restoreAnimated(YES);
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
BMWindow._minimizedWindows.push(minimizedWindow);
|
|
1574
|
+
|
|
1575
|
+
document.body.appendChild(minimizedWindow);
|
|
1576
|
+
|
|
1577
|
+
this._minimizedWindow = minimizedWindow;
|
|
1578
|
+
|
|
1579
|
+
let self = this;
|
|
1580
|
+
|
|
1581
|
+
if (animated) {
|
|
1582
|
+
// Create a copy of the given node which will be used in the animation in place of the original node
|
|
1583
|
+
var node = minimizedWindow;
|
|
1584
|
+
|
|
1585
|
+
var rect = BMRectMakeWithNodeFrame(node);
|
|
1586
|
+
var animationNode = node.cloneNode(YES);
|
|
1587
|
+
|
|
1588
|
+
animationNode.style.position = 'fixed';
|
|
1589
|
+
animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
|
|
1590
|
+
animationNode.style.transform = '';
|
|
1591
|
+
BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
|
|
1592
|
+
|
|
1593
|
+
// Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
|
|
1594
|
+
animationNode.style.pointerEvents = 'none';
|
|
1595
|
+
|
|
1596
|
+
// Compute the transform and apply it first to the window
|
|
1597
|
+
// and then to the animation node, but inversed
|
|
1598
|
+
var frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
|
|
1599
|
+
var transformRect = frame.rectWithTransformToRect(rect);
|
|
1600
|
+
|
|
1601
|
+
BMHook(animationNode, {translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
|
|
1602
|
+
scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height, opacity: 0});
|
|
1603
|
+
|
|
1604
|
+
document.body.appendChild(animationNode);
|
|
1605
|
+
node.style.display = 'none';
|
|
1606
|
+
|
|
1607
|
+
__BMVelocityAnimate(this._window, {opacity: 0, translateX: transformRect.origin.x + 'px', translateY: transformRect.origin.y + 'px',
|
|
1608
|
+
scaleX: transformRect.size.width, scaleY: transformRect.size.height}, {
|
|
1609
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1610
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1611
|
+
display: 'none'
|
|
1612
|
+
}, YES);
|
|
1613
|
+
|
|
1614
|
+
// After finishing the animation, remove the temporary node and restore the original node's display
|
|
1615
|
+
__BMVelocityAnimate(animationNode, {opacity: [1, 0], translateX: ['0px', -transformRect.origin.x + 'px'], translateY: ['0px', -transformRect.origin.y + 'px'],
|
|
1616
|
+
scaleX: [1, 1 / transformRect.size.width], scaleY: [1, 1 / transformRect.size.height]}, {
|
|
1617
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1618
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1619
|
+
complete: function () {
|
|
1620
|
+
node.style.display = 'block';
|
|
1621
|
+
animationNode.remove();
|
|
1622
|
+
|
|
1623
|
+
if (self.delegate && self.delegate.windowDidMinimize) {
|
|
1624
|
+
self.delegate.windowDidMinimize(self);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1628
|
+
}
|
|
1629
|
+
}, YES);
|
|
1630
|
+
}
|
|
1631
|
+
},
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Restores this window. This method will raise an error if this window is modal.
|
|
1635
|
+
* If this window is not minimized, this method will have no effect.
|
|
1636
|
+
* @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated, otherwise it will be instant.
|
|
1637
|
+
* Setting this value to `NO` is currently unsupported.
|
|
1638
|
+
* {
|
|
1639
|
+
* @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been minimized.
|
|
1640
|
+
* }
|
|
1641
|
+
*/
|
|
1642
|
+
restoreAnimated(animated = YES, args = {}) {
|
|
1643
|
+
if (this._modal) throw new Error('A modal window cannot be restored.');
|
|
1644
|
+
if (!this._minimizedWindow) return;
|
|
1645
|
+
|
|
1646
|
+
if (this.delegate && this.delegate.windowWillRestore) {
|
|
1647
|
+
this.delegate.windowWillRestore(this);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
let self = this;
|
|
1651
|
+
|
|
1652
|
+
if (animated) {
|
|
1653
|
+
// Create a copy of the given node which will be used in the animation in place of the original node
|
|
1654
|
+
var node = this._minimizedWindow;
|
|
1655
|
+
var rect = BMRectMakeWithNodeFrame(node);
|
|
1656
|
+
var animationNode = node.cloneNode(YES);
|
|
1657
|
+
|
|
1658
|
+
this._minimizedWindow = undefined;
|
|
1659
|
+
let removedWindow = NO;
|
|
1660
|
+
for (let i = 0, length = BMWindow._minimizedWindows.length, windows = BMWindow._minimizedWindows; i < length; i++) {
|
|
1661
|
+
if (windows[i] == node) {
|
|
1662
|
+
windows.splice(i, 1);
|
|
1663
|
+
i--;
|
|
1664
|
+
length--;
|
|
1665
|
+
removedWindow = YES;
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (removedWindow) {
|
|
1670
|
+
windows[i].style.left = 2 + 258 * i + 'px';
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
if (!this.isKeyWindow) this.becomeKeyWindow();
|
|
1675
|
+
|
|
1676
|
+
animationNode.style.position = 'fixed';
|
|
1677
|
+
animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
|
|
1678
|
+
animationNode.style.transform = '';
|
|
1679
|
+
BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
|
|
1680
|
+
|
|
1681
|
+
// Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
|
|
1682
|
+
animationNode.style.pointerEvents = 'none';
|
|
1683
|
+
|
|
1684
|
+
// Compute the transform and apply it first to the window
|
|
1685
|
+
// and then to the animation node, but inversed
|
|
1686
|
+
var frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
|
|
1687
|
+
var transformRect = frame.rectWithTransformToRect(rect);
|
|
1688
|
+
|
|
1689
|
+
document.body.appendChild(animationNode);
|
|
1690
|
+
node.style.display = 'none';
|
|
1691
|
+
|
|
1692
|
+
__BMVelocityAnimate(this._window, {opacity: 1, translateX: ['0px', transformRect.origin.x + 'px'], translateY: ['0px', transformRect.origin.y + 'px'],
|
|
1693
|
+
scaleX: [1, transformRect.size.width], scaleY: [1, transformRect.size.height]}, {
|
|
1694
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1695
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1696
|
+
display: 'block'
|
|
1697
|
+
}, YES);
|
|
1698
|
+
|
|
1699
|
+
// After finishing the animation, remove the temporary node and restore the original node's display
|
|
1700
|
+
__BMVelocityAnimate(animationNode, {opacity: [0, 1], translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
|
|
1701
|
+
scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height}, {
|
|
1702
|
+
duration: _BMWindowAnimationDurationDefault,
|
|
1703
|
+
easing: _BMWindowAnimationEasingDefault,
|
|
1704
|
+
complete: function () {
|
|
1705
|
+
animationNode.remove();
|
|
1706
|
+
node.remove();
|
|
1707
|
+
|
|
1708
|
+
if (self.delegate && self.delegate.windowDidRestore) {
|
|
1709
|
+
self.delegate.windowDidRestore(self);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1713
|
+
}
|
|
1714
|
+
}, YES);
|
|
1715
|
+
}
|
|
1716
|
+
},
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* Should be invoked to make this window fullscreen.
|
|
1720
|
+
* @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
|
|
1721
|
+
* {
|
|
1722
|
+
* @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been hidden.
|
|
1723
|
+
* }
|
|
1724
|
+
*/
|
|
1725
|
+
enterFullScreenAnimated: function (animated, args) {
|
|
1726
|
+
this._fullScreen = YES;
|
|
1727
|
+
|
|
1728
|
+
if (!this.isKeyWindow) this.becomeKeyWindow();
|
|
1729
|
+
|
|
1730
|
+
animated = (animated === undefined ? YES : animated);
|
|
1731
|
+
|
|
1732
|
+
//__BMVelocityAnimate(this._blocker, 'stop');
|
|
1733
|
+
//__BMVelocityAnimate(this._window, 'stop');
|
|
1734
|
+
|
|
1735
|
+
var self = this;
|
|
1736
|
+
|
|
1737
|
+
this.widthConstraint.constant = window.innerWidth;
|
|
1738
|
+
this.heightConstraint.constant = window.innerHeight;
|
|
1739
|
+
this.leftConstraint.constant = 0;
|
|
1740
|
+
this.topConstraint.constant = 0;
|
|
1741
|
+
|
|
1742
|
+
if (animated) {
|
|
1743
|
+
BMAnimateWithBlock(() => {
|
|
1744
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
|
|
1745
|
+
controller.registerBuiltInPropertiesWithDictionary({left: '0px', top: '0px', width: window.innerWidth + 'px', height: window.innerHeight + 'px'});
|
|
1746
|
+
|
|
1747
|
+
const frame = this.frame.copy();
|
|
1748
|
+
this.layout();
|
|
1749
|
+
|
|
1750
|
+
// When the window contains elements whose intrinsic size must be measured, upon finishing the layout, the window will continue to keep its
|
|
1751
|
+
// temporary frame used for measuring, so it has to be reset prior to the animation starting
|
|
1752
|
+
BMCopyProperties(this.node.style, {left: frame.origin.x + 'px', top: frame.origin.y + 'px', width: frame.size.width + 'px', height: frame.size.height + 'px'})
|
|
1753
|
+
}, {duration: 300, easing: 'easeInOutQuart',
|
|
1754
|
+
complete: function () {
|
|
1755
|
+
if (self.delegate && self.delegate.windowDidEnterFullScreen) self.delegate.windowDidEnterFullScreen(self);
|
|
1756
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1757
|
+
|
|
1758
|
+
self._window.classList.add('BMFullScreenWindow');
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
else {
|
|
1763
|
+
self._window.classList.add('BMFullScreenWindow');
|
|
1764
|
+
if (self.delegate && self.delegate.windowDidEnterFullScreen) self.delegate.windowDidEnterFullScreen(self);
|
|
1765
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
this._resizeHandler = event => {
|
|
1769
|
+
this.widthConstraint.constant = window.innerWidth;
|
|
1770
|
+
this.heightConstraint.constant = window.innerHeight;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
window.addEventListener('resize', this._resizeHandler);
|
|
1774
|
+
|
|
1775
|
+
},
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
/**
|
|
1779
|
+
* Should be invoked to exit full screen mode.
|
|
1780
|
+
* @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
|
|
1781
|
+
* {
|
|
1782
|
+
* @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been hidden.
|
|
1783
|
+
* }
|
|
1784
|
+
*/
|
|
1785
|
+
exitFullScreenAnimated: function (animated, args) {
|
|
1786
|
+
if (!this._fullScreen) return;
|
|
1787
|
+
|
|
1788
|
+
animated = (animated === undefined ? YES : animated);
|
|
1789
|
+
|
|
1790
|
+
//__BMVelocityAnimate(this._blocker, 'stop');
|
|
1791
|
+
//__BMVelocityAnimate(this._window, 'stop');
|
|
1792
|
+
|
|
1793
|
+
var self = this;
|
|
1794
|
+
|
|
1795
|
+
this.widthConstraint.constant = this.frame.width;
|
|
1796
|
+
this.heightConstraint.constant = this.frame.height;
|
|
1797
|
+
this.leftConstraint.constant = this.frame.left;
|
|
1798
|
+
this.topConstraint.constant = this.frame.top;
|
|
1799
|
+
|
|
1800
|
+
if (animated) {
|
|
1801
|
+
self._window.classList.remove('BMFullScreenWindow');
|
|
1802
|
+
var frame = self.frame;
|
|
1803
|
+
BMCopyProperties(self._window.style, {left: '0px', top: '0px', width: window.innerWidth + 'px', height: window.innerHeight + 'px'});
|
|
1804
|
+
/*__BMVelocityAnimate(this._window, {left: frame.origin.x + 'px', top: frame.origin.y + 'px', width: frame.size.width + 'px', height: frame.size.height + 'px'}, {
|
|
1805
|
+
duration: 300,
|
|
1806
|
+
easing: 'easeInOutQuart',
|
|
1807
|
+
complete: function () {
|
|
1808
|
+
if (self.delegate && self.delegate.windowDidExitFullScreen) self.delegate.windowDidExitFullScreen(self);
|
|
1809
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1810
|
+
|
|
1811
|
+
self.frame = self.frame;
|
|
1812
|
+
}
|
|
1813
|
+
});*/
|
|
1814
|
+
|
|
1815
|
+
BMAnimateWithBlock(() => {
|
|
1816
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
|
|
1817
|
+
controller.registerBuiltInProperty('left', {withValue: frame.origin.x + 'px'});
|
|
1818
|
+
controller.registerBuiltInProperty('top', {withValue: frame.origin.y + 'px'});
|
|
1819
|
+
controller.registerBuiltInProperty('width', {withValue: frame.size.width + 'px'});
|
|
1820
|
+
controller.registerBuiltInProperty('height', {withValue: frame.size.height + 'px'});
|
|
1821
|
+
this._fullScreen = NO;
|
|
1822
|
+
this.layout();
|
|
1823
|
+
BMCopyProperties(self._window.style, {left: '0px', top: '0px', width: window.innerWidth + 'px', height: window.innerHeight + 'px'});
|
|
1824
|
+
}, {
|
|
1825
|
+
duration: 300, easing: 'easeInOutQuart',
|
|
1826
|
+
complete: function () {
|
|
1827
|
+
if (self.delegate && self.delegate.windowDidExitFullScreen) self.delegate.windowDidExitFullScreen(self);
|
|
1828
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1829
|
+
|
|
1830
|
+
self.frame = self.frame;
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
else {
|
|
1835
|
+
self._window.classList.remove('BMFullScreenWindow');
|
|
1836
|
+
this._fullScreen = NO;
|
|
1837
|
+
self.frame = self.frame;
|
|
1838
|
+
|
|
1839
|
+
if (self.delegate && self.delegate.windowDidExitFullScreen) self.delegate.windowDidExitFullScreen(self);
|
|
1840
|
+
if (args && args.completionHandler) args.completionHandler();
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
window.removeEventListener('resize', this._resizeHandler);
|
|
1844
|
+
},
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
* Returns `YES` if this window is the key window, `NO` otherwise.
|
|
1848
|
+
*/
|
|
1849
|
+
get isKeyWindow() { // <Boolean>
|
|
1850
|
+
return BMWindow._keyWindow == this;
|
|
1851
|
+
},
|
|
1852
|
+
|
|
1853
|
+
/**
|
|
1854
|
+
* Causes this window to stop being the key window.
|
|
1855
|
+
* If this window is not the key window, this method has no effect.
|
|
1856
|
+
*/
|
|
1857
|
+
resignKeyWindow() {
|
|
1858
|
+
if (BMWindow._keyWindow == this && !this._modal) {
|
|
1859
|
+
this.node.classList.remove('BMKeyWindow');
|
|
1860
|
+
this.node.classList.add('BMWindowInactive');
|
|
1861
|
+
if (this.delegate && this.delegate.windowDidResignKeyWindow) {
|
|
1862
|
+
this.delegate.windowDidResignKeyWindow(this);
|
|
1863
|
+
}
|
|
1864
|
+
BMWindow._keyWindow = undefined;
|
|
1865
|
+
|
|
1866
|
+
for (let toolWindow of this._toolWindows) {
|
|
1867
|
+
toolWindow.hide();
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
|
|
1872
|
+
/**
|
|
1873
|
+
* Causes this window to become the key window.
|
|
1874
|
+
* If this window is minimized when this method is invoked, it will be restored.
|
|
1875
|
+
*/
|
|
1876
|
+
becomeKeyWindow() {
|
|
1877
|
+
let minimized = !!this._minimizedWindow;
|
|
1878
|
+
if (this._minimizedWindow) {
|
|
1879
|
+
this.restoreAnimated(YES);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
if (this._hidden) {
|
|
1883
|
+
this.reveal();
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
if (BMWindow._keyWindow != this) {
|
|
1887
|
+
if (BMWindow._keyWindow) {
|
|
1888
|
+
BMWindow._keyWindow.resignKeyWindow();
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
this.node.classList.add('BMKeyWindow');
|
|
1892
|
+
this.node.classList.remove('BMWindowInactive');
|
|
1893
|
+
BMWindow._keyWindow = this;
|
|
1894
|
+
|
|
1895
|
+
if (!this._modal) {
|
|
1896
|
+
BMWindow._windows.splice(BMWindow._windows.indexOf(this), 1);
|
|
1897
|
+
BMWindow._windows.push(this);
|
|
1898
|
+
BMWindow._windows.slice().reverse().forEach((window, index) => window.node.style.zIndex = BM_WINDOW_Z_INDEX_MAX - index);
|
|
1899
|
+
}
|
|
1900
|
+
else {
|
|
1901
|
+
this._overlay.node.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
|
|
1902
|
+
this.node.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
if (this.delegate && this.delegate.windowDidBecomeKeyWindow) {
|
|
1906
|
+
this.delegate.windowDidBecomeKeyWindow(this);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
for (let toolWindow of this._toolWindows) {
|
|
1910
|
+
toolWindow.reveal();
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
else if (!minimized && !BMWindow._isShowcasing) {
|
|
1914
|
+
BMAnimateWithBlock(() => {
|
|
1915
|
+
BMAnimationContextEnableWebAnimations();
|
|
1916
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
|
|
1917
|
+
controller.registerBuiltInProperty('scaleX', {withValue: 1.05});
|
|
1918
|
+
controller.registerBuiltInProperty('scaleY', {withValue: 1.05});
|
|
1919
|
+
}, {duration: 100, easing: 'easeInQuad', complete: () => {
|
|
1920
|
+
BMAnimateWithBlock(() => {
|
|
1921
|
+
BMAnimationContextEnableWebAnimations();
|
|
1922
|
+
let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
|
|
1923
|
+
controller.registerBuiltInProperty('scaleX', {withValue: 1});
|
|
1924
|
+
controller.registerBuiltInProperty('scaleY', {withValue: 1});
|
|
1925
|
+
}, {duration: 100, easing: 'easeOutQuad'});
|
|
1926
|
+
}});
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* Destroys this window and removes all DOM nodes associated with it.
|
|
1932
|
+
* This instance should not be reused after invoking this method.
|
|
1933
|
+
*/
|
|
1934
|
+
release() {
|
|
1935
|
+
if (BMWindow._keyWindow == this) BMWindow._keyWindow = undefined;
|
|
1936
|
+
|
|
1937
|
+
if (this._boundViewportDidResize) {
|
|
1938
|
+
window.removeEventListener('resize', this._boundViewportDidResize);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
if (!this._modal) {
|
|
1942
|
+
// Remove this window from the global list of windows
|
|
1943
|
+
for (let i = 0, length = BMWindow._windows.length; i < length; i++) {
|
|
1944
|
+
if (BMWindow._windows[i] == this) {
|
|
1945
|
+
BMWindow._windows.splice(i, 1);
|
|
1946
|
+
break;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
if (this._minimizedWindow) {
|
|
1951
|
+
for (let i = 0, length = BMWindow._minimizedWindows.length; i < length; i++) {
|
|
1952
|
+
if (BMWindow._minimizedWindows[i] == this._minimizedWindow) {
|
|
1953
|
+
BMWindow._minimizedWindows.splice(i, 1);
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
this._minimizedWindow.remove();
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
BMView.prototype.release.call(this);
|
|
1962
|
+
this._cancelLayout();
|
|
1963
|
+
this._overlay.release();
|
|
1964
|
+
this._overlay.node.remove();
|
|
1965
|
+
this.node.remove();
|
|
1966
|
+
},
|
|
1967
|
+
|
|
1968
|
+
/**
|
|
1969
|
+
* Hides this window.
|
|
1970
|
+
*/
|
|
1971
|
+
hide() {
|
|
1972
|
+
this._hidden = YES;
|
|
1973
|
+
this.node.classList.add('BMWindowHidden');
|
|
1974
|
+
},
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* Reveals this window.
|
|
1978
|
+
*/
|
|
1979
|
+
reveal() {
|
|
1980
|
+
this._hidden = NO;
|
|
1981
|
+
this.node.classList.remove('BMWindowHidden');
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
});
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* Constructs and returns a window that will display in the given rect.
|
|
1988
|
+
* @param frame <BMRect> The frame.
|
|
1989
|
+
* {
|
|
1990
|
+
* @param toolbar <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will have a toolbar.
|
|
1991
|
+
* @param modal <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will be modal, blocking content behind it.
|
|
1992
|
+
* When set to `NO`, it will be possible to interact with content that is not obstructed by this window.
|
|
1993
|
+
* }
|
|
1994
|
+
* @return <BMWindow> A window.
|
|
1995
|
+
*/
|
|
1996
|
+
export function BMWindowMakeWithFrame(frame, args) {
|
|
1997
|
+
return (new BMWindow()).initWithFrame(frame, args);
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// @endtype
|