bgfx/3rdparty/remotery/vis/Code/TimelineRow.js
Branimir Karadžić f10b16117d Added Remotery.
2015-11-11 11:06:52 -08:00

375 lines
11 KiB
JavaScript

TimelineRow = (function()
{
var row_template = function(){/*
<div class='TimelineRow'>
<div class='TimelineRowCheck TimelineBox'>
<input class='TimelineRowCheckbox' type='checkbox' />
</div>
<div class='TimelineRowExpand TimelineBox NoSelect'>
<div class='TimelineRowExpandButton'>+</div>
</div>
<div class='TimelineRowExpand TimelineBox NoSelect'>
<div class='TimelineRowExpandButton'>-</div>
</div>
<div class='TimelineRowLabel TimelineBox'></div>
<canvas class='TimelineRowCanvas'></canvas>
<div style="clear:left"></div>
</div>
*/}.toString().split(/\n/).slice(1, -1).join("\n");
var CANVAS_Y_OFFSET = 0;
var CANVAS_BORDER = 1;
var SAMPLE_HEIGHT = 16;
var SAMPLE_BORDER = 1;
var SAMPLE_Y_SPACING = SAMPLE_HEIGHT + SAMPLE_BORDER * 2;
var SAMPLE_Y_OFFSET = CANVAS_Y_OFFSET + CANVAS_BORDER + 1;
function TimelineRow(name, width, parent_node, frame_history, check_handler)
{
this.Name = name;
// Create the row HTML and add to the parent
this.ContainerNode = DOM.Node.CreateHTML(row_template);
this.Node = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowData");
this.LabelNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowLabel");
this.LabelNode.innerHTML = name;
this.CheckboxNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCheckbox");
var expand_node_0 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 0);
var expand_node_1 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 1);
this.IncNode = DOM.Node.FindWithClass(expand_node_0, "TimelineRowExpandButton");
this.DecNode = DOM.Node.FindWithClass(expand_node_1, "TimelineRowExpandButton");
this.CanvasNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCanvas");
parent_node.appendChild(this.ContainerNode);
// All sample view windows visible by default
this.CheckboxNode.checked = true;
DOM.Event.AddHandler(this.CheckboxNode, "change", function(evt) { check_handler(name, evt); });
// Manually hook-up events to simulate div:active
// I can't get the equivalent CSS to work in Firefox, so...
DOM.Event.AddHandler(this.IncNode, "mousedown", ExpandButtonDown);
DOM.Event.AddHandler(this.IncNode, "mouseup", ExpandButtonUp);
DOM.Event.AddHandler(this.IncNode, "mouseleave", ExpandButtonUp);
DOM.Event.AddHandler(this.DecNode, "mousedown", ExpandButtonDown);
DOM.Event.AddHandler(this.DecNode, "mouseup", ExpandButtonUp);
DOM.Event.AddHandler(this.DecNode, "mouseleave", ExpandButtonUp);
// Pressing +/i increases/decreases depth
DOM.Event.AddHandler(this.IncNode, "click", Bind(IncDepth, this));
DOM.Event.AddHandler(this.DecNode, "click", Bind(DecDepth, this));
// Setup the canvas
this.Depth = 1;
this.Ctx = this.CanvasNode.getContext("2d");
this.SetSize(width);
this.Clear();
// Frame index to start at when looking for first visible sample
this.StartFrameIndex = 0;
this.FrameHistory = frame_history;
this.VisibleFrames = [ ];
this.VisibleTimeRange = null;
// Sample the mouse is currently hovering over
this.HoverSample = null;
this.HoverSampleDepth = 0;
// Currently selected sample
this.SelectedSample = null;
this.SelectedSampleDepth = 0;
}
TimelineRow.prototype.SetSize = function(width)
{
// Must ALWAYS set the width/height properties together. Setting one on its own has weird side-effects.
this.CanvasNode.width = width;
this.CanvasNode.height = CANVAS_BORDER + SAMPLE_BORDER + SAMPLE_Y_SPACING * this.Depth;
this.Draw(true);
}
TimelineRow.prototype.Clear = function()
{
// Fill box that shows the boundary between thread rows
this.Ctx.fillStyle = "#666"
var b = CANVAS_BORDER;
this.Ctx.fillRect(b, b, this.CanvasNode.width - b * 2, this.CanvasNode.height - b * 2);
}
TimelineRow.prototype.SetVisibleFrames = function(time_range)
{
// Clear previous visible list
this.VisibleFrames = [ ];
if (this.FrameHistory.length == 0)
return;
// Store a copy of the visible time range rather than referencing it
// This prevents external modifications to the time range from affecting rendering/selection
time_range = time_range.Clone();
this.VisibleTimeRange = time_range;
// The frame history can be reset outside this class
// This also catches the overflow to the end of the frame list below when a thread stops sending samples
var max_frame = Math.max(this.FrameHistory.length - 1, 0);
var start_frame_index = Math.min(this.StartFrameIndex, max_frame);
// First do a back-track in case the time range moves negatively
while (start_frame_index > 0)
{
var frame = this.FrameHistory[start_frame_index];
if (time_range.Start_us > frame.StartTime_us)
break;
start_frame_index--;
}
// Then search from this point for the first visible frame
while (start_frame_index < this.FrameHistory.length)
{
var frame = this.FrameHistory[start_frame_index];
if (frame.EndTime_us > time_range.Start_us)
break;
start_frame_index++;
}
// Gather all frames up to the end point
this.StartFrameIndex = start_frame_index;
for (var i = start_frame_index; i < this.FrameHistory.length; i++)
{
var frame = this.FrameHistory[i];
if (frame.StartTime_us > time_range.End_us)
break;
this.VisibleFrames.push(frame);
}
}
TimelineRow.prototype.Draw = function(draw_text)
{
this.Clear();
// Draw all root samples in the visible frame set
for (var i in this.VisibleFrames)
{
var frame = this.VisibleFrames[i];
DrawSamples(this, frame.Samples, 1, draw_text);
}
}
function DrawSamples(self, samples, depth, draw_text)
{
for (var i in samples)
{
var sample = samples[i];
DrawSample(self, sample, depth, draw_text);
if (depth < self.Depth && sample.children != null)
DrawSamples(self, sample.children, depth + 1, draw_text);
}
}
TimelineRow.prototype.UpdateHoverSample = function(mouse_state, x_offset)
{
var hover = GetSampleAtPosition(this, mouse_state, x_offset);
if (hover)
this.SetHoverSample(hover[1], hover[2]);
return hover;
}
TimelineRow.prototype.UpdateSelectedSample = function(mouse_state, x_offset)
{
var select = GetSampleAtPosition(this, mouse_state, x_offset);
if (select)
this.SetSelectedSample(select[1], select[2]);
return select;
}
TimelineRow.prototype.SetHoverSample = function(sample, sample_depth)
{
if (sample != this.HoverSample)
{
// Discard old highlight
// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
var old_sample = this.HoverSample;
var old_sample_depth = this.HoverSampleDepth;
this.HoverSample = null;
this.HoverSampleDepth = 0;
DrawSample(this, old_sample, old_sample_depth, true);
// Add new highlight
this.HoverSample = sample;
this.HoverSampleDepth = sample_depth;
DrawSample(this, sample, sample_depth, true);
}
}
TimelineRow.prototype.SetSelectedSample = function(sample, sample_depth)
{
if (sample != this.SelectedSample)
{
// Discard old highlight
// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
var old_sample = this.SelectedSample;
var old_sample_depth = this.SelectedSampleDepth;
this.SelectedSample = null;
this.SelectedSampleDepth = 0;
DrawSample(this, old_sample, old_sample_depth, true);
// Add new highlight
this.SelectedSample = sample;
this.SelectedSampleDepth = sample_depth;
DrawSample(this, sample, sample_depth, true);
}
}
function ExpandButtonDown(evt)
{
var node = DOM.Event.GetNode(evt);
DOM.Node.AddClass(node, "TimelineRowExpandButtonActive");
}
function ExpandButtonUp(evt)
{
var node = DOM.Event.GetNode(evt);
DOM.Node.RemoveClass(node, "TimelineRowExpandButtonActive");
}
function IncDepth(self)
{
self.Depth++;
self.SetSize(self.CanvasNode.width);
}
function DecDepth(self)
{
if (self.Depth > 1)
{
self.Depth--;
self.SetSize(self.CanvasNode.width);
}
}
function GetSampleAtPosition(self, mouse_state, x_offset)
{
// Mouse movement can occur before any data is sent to a timeline row
var time_range = self.VisibleTimeRange;
if (time_range == null)
return;
// Get the time the mouse is over
var x = mouse_state.Position[0] - x_offset;
var time_us = time_range.Start_us + x / time_range.usPerPixel;
var canvas_y_offset = DOM.Node.GetPosition(self.CanvasNode)[1];
var mouse_y_offset = mouse_state.Position[1] - canvas_y_offset;
mouse_y_offset = Math.min(Math.max(mouse_y_offset, 0), self.CanvasNode.height);
var depth = Math.floor(mouse_y_offset / SAMPLE_Y_SPACING) + 1;
// Search for the first frame to intersect this time
for (var i in self.VisibleFrames)
{
var frame = self.VisibleFrames[i];
if (time_us >= frame.StartTime_us && time_us < frame.EndTime_us)
{
var found_sample = FindSample(self, frame.Samples, time_us, depth, 1);
if (found_sample != null)
return [ frame, found_sample[0], found_sample[1] ];
}
}
return null;
}
function FindSample(self, samples, time_us, target_depth, depth)
{
for (var i in samples)
{
var sample = samples[i];
if (depth == target_depth)
{
if (time_us >= sample.us_start && time_us < sample.us_start + sample.us_length)
return [ sample, depth ];
}
else if (depth < target_depth && sample.children != null)
{
var found_sample = FindSample(self, sample.children, time_us, target_depth, depth + 1);
if (found_sample != null)
return found_sample;
}
}
return null;
}
function DrawSample(self, sample, depth, draw_text)
{
if (sample == null)
return;
// Determine pixel range of the sample
var time_range = self.VisibleTimeRange;
var x0 = time_range.PixelOffset(sample.us_start);
var x1 = x0 + time_range.PixelSize(sample.us_length);
// Clip to padded timeline row
var min_x = 3;
var max_x = self.CanvasNode.width - 5;
x0 = Math.min(Math.max(x0, min_x), max_x);
x1 = Math.min(Math.max(x1, min_x), max_x);
var offset_x = x0;
var offset_y = SAMPLE_Y_OFFSET + (depth - 1) * SAMPLE_Y_SPACING;
var size_x = x1 - x0;
var size_y = SAMPLE_HEIGHT;
// Normal rendering
var ctx = self.Ctx;
ctx.fillStyle = sample.colour;
ctx.fillRect(offset_x, offset_y, size_x, size_y);
// Highlight rendering
var b = (sample == self.HoverSample) ? 255 : 0;
var r = (sample == self.SelectedSample) ? 255 : 0;
if (b + r > 0)
{
ctx.lineWidth = 1;
ctx.strokeStyle = "rgb(" + r + ", 0, " + b + ")";
ctx.strokeRect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1);
}
// Draw sample names clipped to the bounds of the sample
if (draw_text)
{
ctx.save();
ctx.beginPath();
ctx.rect(offset_x + 2.5, offset_y + 1.5, size_x - 5, size_y - 3);
ctx.clip();
ctx.font = "9px verdana";
ctx.fillStyle = "black";
ctx.fillText(sample.name, offset_x + 5.5, offset_y + 1.5 + 9);
ctx.restore();
}
}
return TimelineRow;
})();