Commit fec33d84 authored by Zeynep Cankara's avatar Zeynep Cankara Committed by Commit Bot

[tools][system-analyzer] Implement drag-handlers to timeline panel

This CL adds drag handlers to the timeline panel
to filter events based on the selected portion
of the timeline tracks.

Bug: v8:10644
Change-Id: Ic8a38493eacb62844b3fed5a027f8b1367f2bb59
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2346275
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69669}
parent 943f78a4
......@@ -30,4 +30,17 @@ class SelectTimeEvent extends CustomEvent {
}
}
export { SelectionEvent, FocusEvent, SelectTimeEvent };
class SynchronizeSelectionEvent extends CustomEvent {
static name = 'syncselection';
constructor(start, end) {
super(SynchronizeSelectionEvent.name, { bubbles: true, composed: true });
this.start = start;
this.end = end;
}
}
export {
SelectionEvent, FocusEvent, SelectTimeEvent,
SynchronizeSelectionEvent
};
......@@ -356,6 +356,7 @@ class MapLogEvent extends Event {
return -1;
}
//TODO(zcankara) Add tests for the chunk positions
position(chunks) {
let index = this.chunkIndex(chunks);
let xFrom = (index + 1.5) * kChunkWidth;
......
......@@ -3,10 +3,12 @@
// found in the LICENSE file.
import { defineCustomElement, V8CustomElement } from './helper.mjs';
import { SynchronizeSelectionEvent } from './events.mjs';
import './timeline/timeline-track.mjs';
defineCustomElement('timeline-panel', (templateText) =>
class TimelinePanel extends V8CustomElement {
#timeSelection = { start: 0, end: Infinity };
constructor() {
super(templateText);
this.timelineOverview.addEventListener(
......@@ -15,6 +17,8 @@ defineCustomElement('timeline-panel', (templateText) =>
'overviewupdate', e => this.handleOverviewBackgroundUpdate(e));
this.addEventListener(
'scrolltrack', e => this.handleTrackScroll(e));
this.addEventListener(
SynchronizeSelectionEvent.name, e => this.handleMouseMoveSelection(e));
this.backgroundCanvas = document.createElement('canvas');
this.isLocked = false;
}
......@@ -27,10 +31,11 @@ defineCustomElement('timeline-panel', (templateText) =>
return this.$('#timelineOverviewIndicator');
}
//TODO(zcankara) Remove dependency to timelineCanvas here
get timelineCanvas() {
return this.timelineTracks[0].timelineCanvas;
}
//TODO(zcankara) Remove dependency to timeline here
get timeline() {
return this.timelineTracks[0].timeline;
}
......@@ -52,6 +57,18 @@ defineCustomElement('timeline-panel', (templateText) =>
track.scrollLeft = event.detail;
}
}
handleMouseMoveSelection(event) {
this.selectionMouseMove(event.start, event.end);
}
selectionMouseMove(start, end) {
for (const track of this.timelineTracks) {
track.startTime = start;
track.endTime = end;
}
}
handleTimelineIndicatorMove(event) {
if (event.buttons == 0) return;
let timelineTotalWidth = this.timelineCanvas.offsetWidth;
......
......@@ -15,11 +15,6 @@ found in the LICENSE file. -->
background-color: var(--timeline-background-color);
}
#timeline::-webkit-scrollbar {
width: 0;
background-color: transparent;
}
#timelineLabel {
transform: rotate(90deg);
transform-origin: left bottom 0;
......@@ -85,6 +80,23 @@ found in the LICENSE file. -->
.timeline {
background-color: var(--timeline-background-color);
}
#timeline .rightHandle,
#timeline .leftHandle {
background-color: rgba(200, 200, 200, 0.5);
height: 100%;
width: 5px;
position: absolute;
z-index: 3;
cursor: col-resize;
}
#timeline .selection {
background-color: rgba(133, 68, 163, 0.5);
height: 100%;
position: absolute;
z-index: 2;
}
</style>
<div class="timeline">
<div id="legend">
......@@ -102,6 +114,9 @@ found in the LICENSE file. -->
</table>
</div>
<div id="timeline">
<div class="leftHandle"></div>
<div class="selection"></div>
<div class="rightHandle"></div>
<div id="timelineLabel">Frequency</div>
<div id="timelineChunks"></div>
<canvas id="timelineCanvas"></canvas>
......
......@@ -7,28 +7,135 @@ import {
typeToColor, CSSColor
} from '../helper.mjs';
import { kChunkWidth, kChunkHeight } from '../map-processor.mjs';
import { SelectionEvent, FocusEvent, SelectTimeEvent } from '../events.mjs';
import {
SelectionEvent, FocusEvent, SelectTimeEvent,
SynchronizeSelectionEvent
} from '../events.mjs';
defineCustomElement('./timeline/timeline-track', (templateText) =>
class TimelineTrack extends V8CustomElement {
static SELECTION_OFFSET = 20;
#timeline;
#nofChunks = 400;
#chunks;
#selectedEntry;
#timeToPixel;
#timeSelection = { start: 0, end: Infinity };
#isSelected = false;
#timeStartOffset;
#mouseDownTime;
constructor() {
super(templateText);
this.timeline.addEventListener("mousedown",
e => this.handleTimeRangeSelectionStart(e));
this.timeline.addEventListener("mouseup",
e => this.handleTimeRangeSelectionEnd(e));
this.timeline.addEventListener("scroll",
e => this.handleTimelineScroll(e));
this.timeline.addEventListener("mousedown",
e => this.handleTimeSelectionMouseDown(e));
this.timeline.addEventListener("mouseup",
e => this.handleTimeSelectionMouseUp(e));
this.timeline.addEventListener("mousemove",
e => this.handleTimeSelectionMouseMove(e));
this.backgroundCanvas = document.createElement('canvas');
this.isLocked = false;
}
handleTimeSelectionMouseDown(e) {
if (e.target.className === "chunk") return;
this.#isSelected = true;
this.#mouseDownTime = this.positionToTime(e.clientX);
}
handleTimeSelectionMouseMove(e) {
if (!this.#isSelected) return;
let mouseMoveTime = this.positionToTime(e.clientX);
let startTime = this.#mouseDownTime;
let endTime = mouseMoveTime;
if (this.isOnLeftHandle(e.clientX)) {
startTime = mouseMoveTime;
endTime = this.positionToTime(this.rightHandlePosX);
} else if (this.isOnRightHandle(e.clientX)) {
startTime = this.positionToTime(this.leftHandlePosX);
endTime = mouseMoveTime;
}
this.dispatchEvent(new SynchronizeSelectionEvent(
Math.min(startTime, endTime),
Math.max(startTime, endTime)));
}
handleTimeSelectionMouseUp(e) {
this.#isSelected = false;
this.dispatchEvent(new SelectTimeEvent(this.#timeSelection.start,
this.#timeSelection.end));
}
isOnLeftHandle(posX) {
return (Math.abs(this.leftHandlePosX - posX)
<= TimelineTrack.SELECTION_OFFSET);
}
isOnRightHandle(posX) {
return (Math.abs(this.rightHandlePosX - posX)
<= TimelineTrack.SELECTION_OFFSET);
}
set startTime(value) {
console.assert(
value <= this.#timeSelection.end,
"Selection start time greater than end time!");
this.#timeSelection.start = value;
this.updateSelection();
}
set endTime(value) {
console.assert(
value > this.#timeSelection.start,
"Selection end time smaller than start time!");
this.#timeSelection.end = value;
this.updateSelection();
}
updateSelection() {
let startTimePos = this.timeToPosition(this.#timeSelection.start);
let endTimePos = this.timeToPosition(this.#timeSelection.end);
this.leftHandle.style.left = startTimePos + "px";
this.selection.style.left = startTimePos + "px";
this.rightHandle.style.left = endTimePos + "px";
this.selection.style.width =
Math.abs(this.rightHandlePosX - this.leftHandlePosX) + "px";
}
get leftHandlePosX() {
let leftHandlePosX = this.leftHandle.getBoundingClientRect().x;
return leftHandlePosX;
}
get rightHandlePosX() {
let rightHandlePosX = this.rightHandle.getBoundingClientRect().x;
return rightHandlePosX;
}
// Maps the clicked x position to the x position on timeline canvas
positionOnTimeline(posX) {
let rect = this.timeline.getBoundingClientRect();
let posClickedX = posX - rect.left + this.timeline.scrollLeft;
return posClickedX;
}
positionToTime(posX) {
let posTimelineX = this.positionOnTimeline(posX) + this.#timeStartOffset;
return posTimelineX / this.#timeToPixel;
}
timeToPosition(time) {
let posX = time * this.#timeToPixel;
posX -= this.#timeStartOffset
return posX;
}
get leftHandle() {
return this.$('.leftHandle');
}
get rightHandle() {
return this.$('.rightHandle');
}
get selection() {
return this.$('.selection');
}
get timelineCanvas() {
return this.$('#timelineCanvas');
}
......@@ -144,26 +251,6 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
this.timeline.scrollLeft += offset;
}
handleTimeRangeSelectionStart(e) {
this.#timeSelection.start = this.positionToTime(e.clientX);
}
handleTimeRangeSelectionEnd(e) {
this.#timeSelection.end = this.positionToTime(e.clientX);
this.dispatchEvent(new SelectTimeEvent(
Math.min(this.#timeSelection.start, this.#timeSelection.end),
Math.max(this.#timeSelection.start, this.#timeSelection.end)));
}
positionToTime(posX) {
let rect = this.timeline.getBoundingClientRect();
let timeStartOffset = this.data.startTime * this.#timeToPixel;
let posClickedX =
posX - rect.left + this.timeline.scrollLeft + timeStartOffset;
let selectedTime = posClickedX / this.#timeToPixel;
return selectedTime;
}
handleTimelineScroll(e) {
let horizontal = e.currentTarget.scrollLeft;
this.dispatchEvent(new CustomEvent(
......@@ -228,6 +315,7 @@ defineCustomElement('./timeline/timeline-track', (templateText) =>
let end = this.data.endTime;
let duration = end - start;
this.#timeToPixel = chunks.length * kChunkWidth / duration;
this.#timeStartOffset = start * this.#timeToPixel;
let addTimestamp = (time, name) => {
let timeNode = this.div('timestamp');
timeNode.innerText = name;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment