~mmach/netext73/webkit2gtk

« back to all changes in this revision

Viewing changes to Source/WebInspectorUI/UserInterface/Views/AnimationContentView.js

  • Committer: mmach
  • Date: 2023-06-16 17:21:37 UTC
  • Revision ID: netbit73@gmail.com-20230616172137-2rqx6yr96ga9g3kp
1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2020 Apple Inc. All rights reserved.
 
3
 *
 
4
 * Redistribution and use in source and binary forms, with or without
 
5
 * modification, are permitted provided that the following conditions
 
6
 * are met:
 
7
 * 1. Redistributions of source code must retain the above copyright
 
8
 *    notice, this list of conditions and the following disclaimer.
 
9
 * 2. Redistributions in binary form must reproduce the above copyright
 
10
 *    notice, this list of conditions and the following disclaimer in the
 
11
 *    documentation and/or other materials provided with the distribution.
 
12
 *
 
13
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 
14
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 
15
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 
16
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 
17
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 
18
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 
19
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 
20
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 
21
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 
22
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 
23
 * THE POSSIBILITY OF SUCH DAMAGE.
 
24
 */
 
25
 
 
26
WI.AnimationContentView = class AnimationContentView extends WI.ContentView
 
27
{
 
28
    constructor(representedObject)
 
29
    {
 
30
        console.assert(representedObject instanceof WI.Animation);
 
31
 
 
32
        super(representedObject);
 
33
 
 
34
        this._animationTargetDOMNode = null;
 
35
        this._cachedWidth = NaN;
 
36
 
 
37
        this.element.classList.add("animation");
 
38
    }
 
39
 
 
40
    // Static
 
41
 
 
42
    static get previewHeight()
 
43
    {
 
44
        return 40;
 
45
    }
 
46
 
 
47
    // Public
 
48
 
 
49
    handleRefreshButtonClicked()
 
50
    {
 
51
        this._refreshSubtitle();
 
52
    }
 
53
 
 
54
    // Protected
 
55
 
 
56
    initialLayout()
 
57
    {
 
58
        super.initialLayout();
 
59
 
 
60
        let headerElement = this.element.appendChild(document.createElement("header"));
 
61
 
 
62
        let titlesContainer = headerElement.appendChild(document.createElement("div"));
 
63
        titlesContainer.className = "titles";
 
64
 
 
65
        let titleElement = titlesContainer.appendChild(document.createElement("span"));
 
66
        titleElement.className = "title";
 
67
        titleElement.textContent = this.representedObject.displayName;
 
68
 
 
69
        this._subtitleElement = titlesContainer.appendChild(document.createElement("span"));
 
70
        this._subtitleElement.className = "subtitle";
 
71
 
 
72
        let navigationBar = new WI.NavigationBar;
 
73
 
 
74
        let animationTargetButtonNavigationItem = new WI.ButtonNavigationItem("animation-target", WI.UIString("Animation Target"), "Images/Markup.svg", 16, 16);
 
75
        animationTargetButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
 
76
        WI.addMouseDownContextMenuHandlers(animationTargetButtonNavigationItem.element, this._populateAnimationTargetButtonContextMenu.bind(this));
 
77
        navigationBar.addNavigationItem(animationTargetButtonNavigationItem);
 
78
 
 
79
        headerElement.append(navigationBar.element);
 
80
        this.addSubview(navigationBar);
 
81
 
 
82
        this._previewContainer = this.element.appendChild(document.createElement("div"));
 
83
        this._previewContainer.className = "preview";
 
84
    }
 
85
 
 
86
    layout()
 
87
    {
 
88
        super.layout();
 
89
 
 
90
        this._refreshSubtitle();
 
91
        this._refreshPreview();
 
92
    }
 
93
 
 
94
    sizeDidChange()
 
95
    {
 
96
        super.sizeDidChange();
 
97
 
 
98
        this._cachedWidth = this.element.realOffsetWidth;
 
99
    }
 
100
 
 
101
    attached()
 
102
    {
 
103
        super.attached();
 
104
 
 
105
        this.representedObject.addEventListener(WI.Animation.Event.EffectChanged, this._handleEffectChanged, this);
 
106
        this.representedObject.addEventListener(WI.Animation.Event.TargetChanged, this._handleTargetChanged, this);
 
107
    }
 
108
 
 
109
    detached()
 
110
    {
 
111
        this.representedObject.removeEventListener(WI.Animation.Event.TargetChanged, this._handleTargetChanged, this);
 
112
        this.representedObject.removeEventListener(WI.Animation.Event.EffectChanged, this._handleEffectChanged, this);
 
113
 
 
114
        super.detached();
 
115
    }
 
116
 
 
117
    // Private
 
118
 
 
119
    _refreshSubtitle()
 
120
    {
 
121
        this.representedObject.requestEffectTarget((domNode) => {
 
122
            this._animationTargetDOMNode = domNode;
 
123
 
 
124
            this._subtitleElement.removeChildren();
 
125
            if (domNode)
 
126
                this._subtitleElement.appendChild(WI.linkifyNodeReference(domNode));
 
127
        });
 
128
    }
 
129
 
 
130
    _refreshPreview()
 
131
    {
 
132
        this._previewContainer.removeChildren();
 
133
 
 
134
        let keyframes = this.representedObject.keyframes;
 
135
        if (!keyframes.length) {
 
136
            let span = this._previewContainer.appendChild(document.createElement("span"));
 
137
            span.textContent = WI.UIString("This animation has no keyframes.");
 
138
            return;
 
139
        }
 
140
 
 
141
        let startDelay = this.representedObject.startDelay || 0;
 
142
        let iterationDuration = this.representedObject.iterationDuration || 0;
 
143
        let endDelay = this.representedObject.endDelay || 0;
 
144
        let totalDuration = startDelay + iterationDuration + endDelay;
 
145
        if (totalDuration === 0) {
 
146
            let span = this._previewContainer.appendChild(document.createElement("span"));
 
147
            span.textContent = WI.UIString("This animation has no duration.");
 
148
            return;
 
149
        }
 
150
 
 
151
        const previewHeight = WI.AnimationContentView.previewHeight;
 
152
 
 
153
        const markerHeadRadius = 4;
 
154
        const markerHeadPadding = 2;
 
155
 
 
156
        // Squeeze the entire preview so that markers aren't cut off.
 
157
        const squeezeXStart = (iterationDuration && startDelay) ? 0 : markerHeadRadius + markerHeadPadding;
 
158
        const squeezeXEnd = (iterationDuration && endDelay) ? 0 : markerHeadRadius + markerHeadPadding;
 
159
        const squeezeYStart = markerHeadRadius + (markerHeadPadding * 2);
 
160
 
 
161
        // Move the easing line down to cut off the bottom border.
 
162
        const adjustEasingY = 0.5;
 
163
 
 
164
        let secondsPerPixel = this._cachedWidth / totalDuration;
 
165
 
 
166
        startDelay *= secondsPerPixel;
 
167
        iterationDuration = (iterationDuration * secondsPerPixel) - squeezeXStart - squeezeXEnd;
 
168
        endDelay *= secondsPerPixel;
 
169
 
 
170
        let svg = this._previewContainer.appendChild(createSVGElement("svg"));
 
171
        svg.setAttribute("viewBox", `0 0 ${this._cachedWidth} ${previewHeight}`);
 
172
 
 
173
        function addTitle(parent, title) {
 
174
            let titleElement = parent.appendChild(createSVGElement("title"));
 
175
            titleElement.textContent = title;
 
176
        }
 
177
 
 
178
        if (startDelay) {
 
179
            let startDelayContainer = svg.appendChild(createSVGElement("g"));
 
180
            startDelayContainer.classList.add("delay", "start");
 
181
 
 
182
            let startDelayLine = startDelayContainer.appendChild(createSVGElement("line"));
 
183
            startDelayLine.setAttribute("y1", (previewHeight + squeezeYStart) / 2);
 
184
            startDelayLine.setAttribute("x2", startDelay);
 
185
            startDelayLine.setAttribute("y2", (previewHeight + squeezeYStart) / 2);
 
186
 
 
187
            let startDelayElement = startDelayContainer.appendChild(createSVGElement("rect"));
 
188
            startDelayElement.setAttribute("width", startDelay);
 
189
            startDelayElement.setAttribute("height", previewHeight);
 
190
 
 
191
            const startDelayTitleFormat = WI.UIString("Start Delay %s", "Web Animation Start Delay Tooltip", "Tooltip for section of graph representing delay before a web animation begins applying styles");
 
192
            addTitle(startDelayElement, startDelayTitleFormat.format(Number.secondsToString(this.representedObject.startDelay / 1000)));
 
193
        }
 
194
 
 
195
        if (endDelay) {
 
196
            let endDelayContainer = svg.appendChild(createSVGElement("g"));
 
197
            endDelayContainer.setAttribute("transform", `translate(${startDelay + iterationDuration + squeezeXStart}, 0)`);
 
198
            endDelayContainer.classList.add("delay", "end");
 
199
 
 
200
            let endDelayLine = endDelayContainer.appendChild(createSVGElement("line"));
 
201
            endDelayLine.setAttribute("y1", (previewHeight + squeezeYStart) / 2);
 
202
            endDelayLine.setAttribute("x2", endDelay);
 
203
            endDelayLine.setAttribute("y2", (previewHeight + squeezeYStart) / 2);
 
204
 
 
205
            let endDelayElement = endDelayContainer.appendChild(createSVGElement("rect"));
 
206
            endDelayElement.setAttribute("width", startDelay + iterationDuration + endDelay);
 
207
            endDelayElement.setAttribute("height", previewHeight);
 
208
 
 
209
            const endDelayTitleFormat = WI.UIString("End Delay %s", "Web Animation End Delay Tooltip", "Tooltip for section of graph representing delay after a web animation finishes applying styles");
 
210
            addTitle(endDelayElement, endDelayTitleFormat.format(Number.secondsToString(this.representedObject.endDelay / 1000)));
 
211
        }
 
212
 
 
213
        if (iterationDuration) {
 
214
            let timingFunction = this.representedObject.timingFunction;
 
215
 
 
216
            let activeDurationContainer = svg.appendChild(createSVGElement("g"));
 
217
            activeDurationContainer.classList.add("active");
 
218
            activeDurationContainer.setAttribute("transform", `translate(${startDelay + squeezeXStart}, ${squeezeYStart})`);
 
219
 
 
220
            const startY = 0;
 
221
            const endY = previewHeight - squeezeYStart;
 
222
            const height = endY - startY;
 
223
 
 
224
            for (let [keyframeA, keyframeB] of keyframes.adjacencies()) {
 
225
                let startX = iterationDuration * keyframeA.offset;
 
226
                let endX = iterationDuration * keyframeB.offset;
 
227
                let width = endX - startX;
 
228
 
 
229
                let easing = keyframeA.easing || timingFunction;
 
230
 
 
231
                let easingContainer = activeDurationContainer.appendChild(createSVGElement("g"));
 
232
                easingContainer.classList.add("easing");
 
233
                easingContainer.setAttribute("transform", `translate(${startX}, ${startY})`);
 
234
 
 
235
                let x1 = easing.inPoint.x * width;
 
236
                let y1 = ((1 - easing.inPoint.y) * height) + adjustEasingY;
 
237
                let x2 = easing.outPoint.x * width;
 
238
                let y2 = ((1 - easing.outPoint.y) * height) + adjustEasingY;
 
239
 
 
240
                let easingPath = easingContainer.appendChild(createSVGElement("path"));
 
241
                easingPath.setAttribute("d", `M 0 ${height + adjustEasingY} C ${x1} ${y1} ${x2} ${y2} ${width} ${adjustEasingY} V ${height + adjustEasingY} Z`);
 
242
 
 
243
                let titleRect = easingContainer.appendChild(createSVGElement("rect"));
 
244
                titleRect.setAttribute("width", width);
 
245
                titleRect.setAttribute("height", height);
 
246
 
 
247
                addTitle(titleRect, easing.toString());
 
248
            }
 
249
 
 
250
            for (let keyframe of keyframes) {
 
251
                let x = iterationDuration * keyframe.offset;
 
252
 
 
253
                let keyframeContainer = activeDurationContainer.appendChild(createSVGElement("g"));
 
254
                keyframeContainer.classList.add("keyframe");
 
255
                keyframeContainer.setAttribute("transform", `translate(${x}, ${startY})`);
 
256
 
 
257
                let keyframeMarkerHead = keyframeContainer.appendChild(createSVGElement("circle"));
 
258
                keyframeMarkerHead.setAttribute("r", markerHeadRadius);
 
259
 
 
260
                let keyframeMarkerLine = keyframeContainer.appendChild(createSVGElement("line"));
 
261
                keyframeMarkerLine.setAttribute("y1", height);
 
262
 
 
263
                let titleRect = keyframeContainer.appendChild(createSVGElement("rect"));
 
264
                titleRect.setAttribute("x", -1 * (markerHeadRadius + markerHeadPadding));
 
265
                titleRect.setAttribute("y", -1 * squeezeYStart);
 
266
                titleRect.setAttribute("width", (markerHeadRadius + markerHeadPadding) * 2);
 
267
                titleRect.setAttribute("height", height + squeezeYStart);
 
268
 
 
269
                addTitle(titleRect, keyframe.style);
 
270
            }
 
271
        }
 
272
    }
 
273
 
 
274
    _handleEffectChanged(event)
 
275
    {
 
276
        this._refreshPreview();
 
277
    }
 
278
 
 
279
    _handleTargetChanged(event)
 
280
    {
 
281
        this._refreshSubtitle();
 
282
    }
 
283
 
 
284
    _populateAnimationTargetButtonContextMenu(contextMenu)
 
285
    {
 
286
        contextMenu.appendItem(WI.UIString("Log Animation"), () => {
 
287
            WI.RemoteObject.resolveAnimation(this.representedObject, WI.RuntimeManager.ConsoleObjectGroup, (remoteObject) => {
 
288
                if (!remoteObject)
 
289
                    return;
 
290
 
 
291
                const text = WI.UIString("Selected Animation", "Appears as a label when a given web animation is logged to the Console");
 
292
                const addSpecialUserLogClass = true;
 
293
                WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass);
 
294
            });
 
295
        });
 
296
 
 
297
        contextMenu.appendSeparator();
 
298
 
 
299
        if (this._animationTargetDOMNode)
 
300
            WI.appendContextMenuItemsForDOMNode(contextMenu, this._animationTargetDOMNode);
 
301
    }
 
302
};