1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 tvcm.requireStylesheet('cc.picture_ops_list_view');
9 tvcm.require('cc.constants');
10 tvcm.require('cc.selection');
11 tvcm.require('tvcm.ui.list_view');
12 tvcm.require('tvcm.ui.dom_helpers');
14 tvcm.exportTo('cc', function() {
15 var OPS_TIMING_ITERATIONS = 3; // Iterations to average op timing info over.
16 var ANNOTATION = 'Comment';
17 var BEGIN_ANNOTATION = 'BeginCommentGroup';
18 var END_ANNOTATION = 'EndCommentGroup';
19 var ANNOTATION_ID = 'ID: ';
20 var ANNOTATION_CLASS = 'CLASS: ';
21 var ANNOTATION_TAG = 'TAG: ';
23 var constants = cc.constants;
28 var PictureOpsListView = tvcm.ui.define('picture-ops-list-view');
30 PictureOpsListView.prototype = {
31 __proto__: HTMLUnknownElement.prototype,
33 decorate: function() {
34 this.opsList_ = new tvcm.ui.ListView();
35 this.appendChild(this.opsList_);
37 this.selectedOp_ = undefined;
38 this.selectedOpIndex_ = undefined;
39 this.opsList_.addEventListener(
40 'selection-changed', this.onSelectionChanged_.bind(this));
42 this.picture_ = undefined;
49 set picture(picture) {
50 this.picture_ = picture;
51 this.updateContents_();
54 updateContents_: function() {
55 this.opsList_.clear();
60 var ops = this.picture_.getOps();
64 ops = this.picture_.tagOpsWithTimings(ops);
66 ops = this.opsTaggedWithAnnotations_(ops);
68 for (var i = 0; i < ops.length; i++) {
70 var item = document.createElement('div');
71 item.opIndex = op.opIndex;
72 item.textContent = i + ') ' + op.cmd_string;
74 // Display the element info associated with the op, if available.
75 if (op.elementInfo.tag || op.elementInfo.id || op.elementInfo.class) {
76 var elementInfo = document.createElement('span');
77 elementInfo.classList.add('elementInfo');
78 var tag = op.elementInfo.tag ? op.elementInfo.tag : 'unknown';
79 var id = op.elementInfo.id ? 'id=' + op.elementInfo.id : undefined;
80 var className = op.elementInfo.class ? 'class=' +
81 op.elementInfo.class : undefined;
82 elementInfo.textContent =
83 '<' + tag + (id ? ' ' : '') +
84 (id ? id : '') + (className ? ' ' : '') +
85 (className ? className : '') + '>';
86 item.appendChild(elementInfo);
89 // Display each of the Skia ops.
90 op.info.forEach(function(info) {
91 var infoItem = document.createElement('div');
92 infoItem.textContent = info;
93 item.appendChild(infoItem);
96 // Display the op timing, if available.
97 if (op.cmd_time && op.cmd_time >= 0.0001) {
98 var time = document.createElement('span');
99 time.classList.add('time');
100 var rounded = op.cmd_time.toFixed(4);
101 time.textContent = '(' + rounded + 'ms)';
102 item.appendChild(time);
105 this.opsList_.appendChild(item);
109 onSelectionChanged_: function(e) {
110 var beforeSelectedOp = true;
112 // Deselect on re-selection.
113 if (this.opsList_.selectedElement === this.selectedOp_) {
114 this.opsList_.selectedElement = undefined;
115 beforeSelectedOp = false;
116 this.selectedOpIndex_ = undefined;
119 this.selectedOp_ = this.opsList_.selectedElement;
121 // Set selection on all previous ops.
122 var ops = this.opsList_.children;
123 for (var i = 0; i < ops.length; i++) {
125 if (op === this.selectedOp_) {
126 beforeSelectedOp = false;
127 this.selectedOpIndex_ = op.opIndex;
128 } else if (beforeSelectedOp) {
129 op.setAttribute('beforeSelection', 'beforeSelection');
131 op.removeAttribute('beforeSelection');
135 tvcm.dispatchSimpleEvent(this, 'selection-changed', false);
139 return this.opsList_.children.length;
142 get selectedOpIndex() {
143 return this.selectedOpIndex_;
146 set selectedOpIndex(s) {
147 this.selectedOpIndex_ = s;
149 if (s === undefined) {
150 this.opsList_.selectedElement = this.selectedOp_;
151 this.onSelectionChanged_();
153 if (s < 0) throw new Error('Invalid index');
154 if (s >= this.numOps) throw new Error('Invalid index');
155 this.opsList_.selectedElement = this.opsList_.getElementByIndex(s + 1);
156 tvcm.scrollIntoViewIfNeeded(this.opsList_.selectedElement);
161 * Return Skia operations tagged by annotation.
163 * The ops returned from Picture.getOps() contain both Skia ops and
164 * annotations threaded together. This function removes all annotations
165 * from the list and tags each op with the associated annotations.
166 * Additionally, the last {tag, id, class} is stored as elementInfo on
169 * @param {Array} ops Array of Skia operations and annotations.
170 * @return {Array} Skia ops where op.annotations contains the associated
171 * annotations for a given op.
173 opsTaggedWithAnnotations_: function(ops) {
174 // This algorithm works by walking all the ops and pushing any
175 // annotations onto a stack. When a non-annotation op is found, the
176 // annotations stack is traversed and stored with the op.
177 var annotationGroups = new Array();
178 var opsWithoutAnnotations = new Array();
179 for (var opIndex = 0; opIndex < ops.length; opIndex++) {
180 var op = ops[opIndex];
181 op.opIndex = opIndex;
182 switch (op.cmd_string) {
183 case BEGIN_ANNOTATION:
184 annotationGroups.push(new Array());
187 annotationGroups.pop();
190 annotationGroups[annotationGroups.length - 1].push(op);
193 var annotations = new Array();
194 var elementInfo = {};
195 annotationGroups.forEach(function(annotationGroup) {
197 annotationGroup.forEach(function(annotation) {
198 annotation.info.forEach(function(info) {
199 if (info.indexOf(ANNOTATION_TAG) != -1)
200 elementInfo.tag = info.substring(
201 info.indexOf(ANNOTATION_TAG) +
202 ANNOTATION_TAG.length).toLowerCase();
203 else if (info.indexOf(ANNOTATION_ID) != -1)
204 elementInfo.id = info.substring(
205 info.indexOf(ANNOTATION_ID) +
206 ANNOTATION_ID.length);
207 else if (info.indexOf(ANNOTATION_CLASS) != -1)
208 elementInfo.class = info.substring(
209 info.indexOf(ANNOTATION_CLASS) +
210 ANNOTATION_CLASS.length);
212 annotations.push(info);
216 op.annotations = annotations;
217 op.elementInfo = elementInfo;
218 opsWithoutAnnotations.push(op);
222 return opsWithoutAnnotations;
227 PictureOpsListView: PictureOpsListView