TimingPath.js (13754B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 PureComponent, 9 } = require("resource://devtools/client/shared/vendor/react.mjs"); 10 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 11 12 // Show max 10 iterations for infinite animations 13 // to give users a clue that the animation does repeat. 14 const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10; 15 16 class TimingPath extends PureComponent { 17 /** 18 * Render a graph of given parameters and return as <path> element list. 19 * 20 * @param {object} state 21 * State of animation. 22 * @param {SummaryGraphHelper} helper 23 * Instance of SummaryGraphHelper. 24 * @return {Array} 25 * list of <path> element. 26 */ 27 renderGraph(state, helper) { 28 // Starting time of main iteration. 29 let mainIterationStartTime = 0; 30 let iterationStart = state.iterationStart; 31 let iterationCount = state.iterationCount ? state.iterationCount : Infinity; 32 33 const pathList = []; 34 35 // Append delay. 36 if (state.delay > 0) { 37 this.renderDelay(pathList, state, helper); 38 mainIterationStartTime = state.delay; 39 } else { 40 const negativeDelayCount = -state.delay / state.duration; 41 // Move to forward the starting point for negative delay. 42 iterationStart += negativeDelayCount; 43 // Consume iteration count by negative delay. 44 if (iterationCount !== Infinity) { 45 iterationCount -= negativeDelayCount; 46 } 47 } 48 49 if (state.duration === Infinity) { 50 this.renderInfinityDuration( 51 pathList, 52 state, 53 mainIterationStartTime, 54 helper 55 ); 56 return pathList; 57 } 58 59 // Append 1st section of iterations, 60 // This section is only useful in cases where iterationStart has decimals. 61 // e.g. 62 // if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75. 63 const firstSectionCount = 64 iterationStart % 1 === 0 65 ? 0 66 : Math.min(1 - (iterationStart % 1), iterationCount); 67 68 if (firstSectionCount) { 69 this.renderFirstIteration( 70 pathList, 71 state, 72 mainIterationStartTime, 73 firstSectionCount, 74 helper 75 ); 76 } 77 78 if (iterationCount === Infinity) { 79 // If the animation repeats infinitely, 80 // we fill the remaining area with iteration paths. 81 this.renderInfinity( 82 pathList, 83 state, 84 mainIterationStartTime, 85 firstSectionCount, 86 helper 87 ); 88 } else { 89 // Otherwise, we show remaining iterations, endDelay and fill. 90 91 // Append forwards fill-mode. 92 if (state.fill === "both" || state.fill === "forwards") { 93 this.renderForwardsFill( 94 pathList, 95 state, 96 mainIterationStartTime, 97 iterationCount, 98 helper 99 ); 100 } 101 102 // Append middle section of iterations. 103 // e.g. 104 // if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2. 105 const middleSectionCount = Math.floor(iterationCount - firstSectionCount); 106 this.renderMiddleIterations( 107 pathList, 108 state, 109 mainIterationStartTime, 110 firstSectionCount, 111 middleSectionCount, 112 helper 113 ); 114 115 // Append last section of iterations, if there is remaining iteration. 116 // e.g. 117 // if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25. 118 const lastSectionCount = 119 iterationCount - middleSectionCount - firstSectionCount; 120 if (lastSectionCount) { 121 this.renderLastIteration( 122 pathList, 123 state, 124 mainIterationStartTime, 125 firstSectionCount, 126 middleSectionCount, 127 lastSectionCount, 128 helper 129 ); 130 } 131 132 // Append endDelay. 133 if (state.endDelay > 0) { 134 this.renderEndDelay( 135 pathList, 136 state, 137 mainIterationStartTime, 138 iterationCount, 139 helper 140 ); 141 } 142 } 143 return pathList; 144 } 145 146 /** 147 * Render 'delay' part in animation and add a <path> element to given pathList. 148 * 149 * @param {Array} pathList 150 * Add rendered <path> element to this array. 151 * @param {object} state 152 * State of animation. 153 * @param {SummaryGraphHelper} helper 154 * Instance of SummaryGraphHelper. 155 */ 156 renderDelay(pathList, state, helper) { 157 const startSegment = helper.getSegment(0); 158 const endSegment = { x: state.delay, y: startSegment.y }; 159 const segments = [startSegment, endSegment]; 160 pathList.push( 161 dom.path({ 162 className: "animation-delay-path", 163 d: helper.toPathString(segments), 164 }) 165 ); 166 } 167 168 /** 169 * Render 1st section of iterations and add a <path> element to given pathList. 170 * This section is only useful in cases where iterationStart has decimals. 171 * 172 * @param {Array} pathList 173 * Add rendered <path> element to this array. 174 * @param {object} state 175 * State of animation. 176 * @param {number} mainIterationStartTime 177 * Start time of main iteration. 178 * @param {number} firstSectionCount 179 * Iteration count of first section. 180 * @param {SummaryGraphHelper} helper 181 * Instance of SummaryGraphHelper. 182 */ 183 renderFirstIteration( 184 pathList, 185 state, 186 mainIterationStartTime, 187 firstSectionCount, 188 helper 189 ) { 190 const startTime = mainIterationStartTime; 191 const endTime = startTime + firstSectionCount * state.duration; 192 const segments = helper.createPathSegments(startTime, endTime); 193 pathList.push( 194 dom.path({ 195 className: "animation-iteration-path", 196 d: helper.toPathString(segments), 197 }) 198 ); 199 } 200 201 /** 202 * Render middle iterations and add <path> elements to given pathList. 203 * 204 * @param {Array} pathList 205 * Add rendered <path> elements to this array. 206 * @param {object} state 207 * State of animation. 208 * @param {number} mainIterationStartTime 209 * Starting time of main iteration. 210 * @param {number} firstSectionCount 211 * Iteration count of first section. 212 * @param {number} middleSectionCount 213 * Iteration count of middle section. 214 * @param {SummaryGraphHelper} helper 215 * Instance of SummaryGraphHelper. 216 */ 217 renderMiddleIterations( 218 pathList, 219 state, 220 mainIterationStartTime, 221 firstSectionCount, 222 middleSectionCount, 223 helper 224 ) { 225 const offset = mainIterationStartTime + firstSectionCount * state.duration; 226 for (let i = 0; i < middleSectionCount; i++) { 227 // Get the path segments of each iteration. 228 const startTime = offset + i * state.duration; 229 const endTime = startTime + state.duration; 230 const segments = helper.createPathSegments(startTime, endTime); 231 pathList.push( 232 dom.path({ 233 className: "animation-iteration-path", 234 d: helper.toPathString(segments), 235 }) 236 ); 237 } 238 } 239 240 /** 241 * Render last section of iterations and add a <path> element to given pathList. 242 * This section is only useful in cases where iterationStart has decimals. 243 * 244 * @param {Array} pathList 245 * Add rendered <path> elements to this array. 246 * @param {object} state 247 * State of animation. 248 * @param {number} mainIterationStartTime 249 * Starting time of main iteration. 250 * @param {number} firstSectionCount 251 * Iteration count of first section. 252 * @param {number} middleSectionCount 253 * Iteration count of middle section. 254 * @param {number} lastSectionCount 255 * Iteration count of last section. 256 * @param {SummaryGraphHelper} helper 257 * Instance of SummaryGraphHelper. 258 */ 259 renderLastIteration( 260 pathList, 261 state, 262 mainIterationStartTime, 263 firstSectionCount, 264 middleSectionCount, 265 lastSectionCount, 266 helper 267 ) { 268 const startTime = 269 mainIterationStartTime + 270 (firstSectionCount + middleSectionCount) * state.duration; 271 const endTime = startTime + lastSectionCount * state.duration; 272 const segments = helper.createPathSegments(startTime, endTime); 273 pathList.push( 274 dom.path({ 275 className: "animation-iteration-path", 276 d: helper.toPathString(segments), 277 }) 278 ); 279 } 280 281 /** 282 * Render infinity iterations and add <path> elements to given pathList. 283 * 284 * @param {Array} pathList 285 * Add rendered <path> elements to this array. 286 * @param {object} state 287 * State of animation. 288 * @param {number} mainIterationStartTime 289 * Starting time of main iteration. 290 * @param {number} firstSectionCount 291 * Iteration count of first section. 292 * @param {SummaryGraphHelper} helper 293 * Instance of SummaryGraphHelper. 294 */ 295 renderInfinity( 296 pathList, 297 state, 298 mainIterationStartTime, 299 firstSectionCount, 300 helper 301 ) { 302 // Calculate the number of iterations to display, 303 // with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS 304 let uncappedInfinityIterationCount = 305 (helper.totalDuration - firstSectionCount * state.duration) / 306 state.duration; 307 // If there is a small floating point error resulting in, e.g. 1.0000001 308 // ceil will give us 2 so round first. 309 uncappedInfinityIterationCount = parseFloat( 310 uncappedInfinityIterationCount.toPrecision(6) 311 ); 312 const infinityIterationCount = Math.min( 313 MAX_INFINITE_ANIMATIONS_ITERATIONS, 314 Math.ceil(uncappedInfinityIterationCount) 315 ); 316 317 // Append first full iteration path. 318 const firstStartTime = 319 mainIterationStartTime + firstSectionCount * state.duration; 320 const firstEndTime = firstStartTime + state.duration; 321 const firstSegments = helper.createPathSegments( 322 firstStartTime, 323 firstEndTime 324 ); 325 pathList.push( 326 dom.path({ 327 className: "animation-iteration-path", 328 d: helper.toPathString(firstSegments), 329 }) 330 ); 331 332 // Append other iterations. We can copy first segments. 333 const isAlternate = state.direction.match(/alternate/); 334 for (let i = 1; i < infinityIterationCount; i++) { 335 const startTime = firstStartTime + i * state.duration; 336 let segments; 337 if (isAlternate && i % 2) { 338 // Copy as reverse. 339 segments = firstSegments.map(segment => { 340 return { x: firstEndTime - segment.x + startTime, y: segment.y }; 341 }); 342 } else { 343 // Copy as is. 344 segments = firstSegments.map(segment => { 345 return { x: segment.x - firstStartTime + startTime, y: segment.y }; 346 }); 347 } 348 pathList.push( 349 dom.path({ 350 className: "animation-iteration-path infinity", 351 d: helper.toPathString(segments), 352 }) 353 ); 354 } 355 } 356 357 /** 358 * Render infinity duration. 359 * 360 * @param {Array} pathList 361 * Add rendered <path> elements to this array. 362 * @param {object} state 363 * State of animation. 364 * @param {number} mainIterationStartTime 365 * Starting time of main iteration. 366 * @param {SummaryGraphHelper} helper 367 * Instance of SummaryGraphHelper. 368 */ 369 renderInfinityDuration(pathList, state, mainIterationStartTime, helper) { 370 const startSegment = helper.getSegment(mainIterationStartTime); 371 const endSegment = { x: helper.totalDuration, y: startSegment.y }; 372 const segments = [startSegment, endSegment]; 373 pathList.push( 374 dom.path({ 375 className: "animation-iteration-path infinity-duration", 376 d: helper.toPathString(segments), 377 }) 378 ); 379 } 380 381 /** 382 * Render 'endDelay' part in animation and add a <path> element to given pathList. 383 * 384 * @param {Array} pathList 385 * Add rendered <path> element to this array. 386 * @param {object} state 387 * State of animation. 388 * @param {number} mainIterationStartTime 389 * Starting time of main iteration. 390 * @param {number} iterationCount 391 * Iteration count of whole animation. 392 * @param {SummaryGraphHelper} helper 393 * Instance of SummaryGraphHelper. 394 */ 395 renderEndDelay( 396 pathList, 397 state, 398 mainIterationStartTime, 399 iterationCount, 400 helper 401 ) { 402 const startTime = mainIterationStartTime + iterationCount * state.duration; 403 const startSegment = helper.getSegment(startTime); 404 const endSegment = { x: startTime + state.endDelay, y: startSegment.y }; 405 pathList.push( 406 dom.path({ 407 className: "animation-enddelay-path", 408 d: helper.toPathString([startSegment, endSegment]), 409 }) 410 ); 411 } 412 413 /** 414 * Render 'fill' for forwards part in animation and 415 * add a <path> element to given pathList. 416 * 417 * @param {Array} pathList 418 * Add rendered <path> element to this array. 419 * @param {object} state 420 * State of animation. 421 * @param {number} mainIterationStartTime 422 * Starting time of main iteration. 423 * @param {number} iterationCount 424 * Iteration count of whole animation. 425 * @param {SummaryGraphHelper} helper 426 * Instance of SummaryGraphHelper. 427 */ 428 renderForwardsFill( 429 pathList, 430 state, 431 mainIterationStartTime, 432 iterationCount, 433 helper 434 ) { 435 const startTime = 436 mainIterationStartTime + 437 iterationCount * state.duration + 438 (state.endDelay > 0 ? state.endDelay : 0); 439 const startSegment = helper.getSegment(startTime); 440 const endSegment = { x: helper.totalDuration, y: startSegment.y }; 441 pathList.push( 442 dom.path({ 443 className: "animation-fill-forwards-path", 444 d: helper.toPathString([startSegment, endSegment]), 445 }) 446 ); 447 } 448 } 449 450 module.exports = TimingPath;