// // TODO: Use WebGL and instancing for quicker renders // TimelineWindow = (function() { var BORDER = 10; var ROW_START_SIZE = 210; var ROW_END_SIZE = 20; // make room for scrollbar var box_template = "
"; function TimelineWindow(wm, settings, server, check_handler) { this.Settings = settings; // Ordered list of thread rows on the timeline this.ThreadRows = [ ]; // Create window and containers this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100); this.Window.ShowNoAnim(); this.TimelineContainer = this.Window.AddControlNew(new WM.Container(10, 10, 800, 160)); DOM.Node.AddClass(this.TimelineContainer.Node, "TimelineContainer"); var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; DOM.Event.AddHandler(this.TimelineContainer.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); // Setup timeline manipulation this.MouseDown = false; this.LastMouseState = null; this.TimelineMoved = false; this.OnHoverHandler = null; this.OnSelectedHandler = null; DOM.Event.AddHandler(this.TimelineContainer.Node, "mousedown", Bind(OnMouseDown, this)); DOM.Event.AddHandler(this.TimelineContainer.Node, "mouseup", Bind(OnMouseUp, this)); DOM.Event.AddHandler(this.TimelineContainer.Node, "mousemove", Bind(OnMouseMove, this)); // Set time range AFTER the window has been created, as it uses the window to determine pixel coverage this.TimeRange = new PixelTimeRange(0, 200 * 1000, RowWidth(this)); this.CheckHandler = check_handler; this.Window.SetOnResize(Bind(OnUserResize, this)); } TimelineWindow.prototype.SetOnHover = function(handler) { this.OnHoverHandler = handler; } TimelineWindow.prototype.SetOnSelected = function(handler) { this.OnSelectedHandler = handler; } TimelineWindow.prototype.WindowResized = function(width, height, top_window) { // Resize window var top = top_window.Position[1] + top_window.Size[1] + 10; this.Window.SetPosition(10, top); this.Window.SetSize(width - 2 * 10, 260); ResizeInternals(this); } TimelineWindow.prototype.ResetTimeRange = function() { this.TimeRange.SetStart(0); } TimelineWindow.prototype.OnSamples = function(thread_name, frame_history) { // Shift the timeline to the last entry on this thread // As multiple threads come through here with different end frames, only do this for the latest var last_frame = frame_history[frame_history.length - 1]; if (last_frame.EndTime_us > this.TimeRange.End_us) this.TimeRange.SetEnd(last_frame.EndTime_us); // Search for the index of this thread var thread_index = -1; for (var i in this.ThreadRows) { if (this.ThreadRows[i].Name == thread_name) { thread_index = i; break; } } // If this thread has not been seen before, add a new row to the list and re-sort if (thread_index == -1) { var row = new TimelineRow(thread_name, RowWidth(this), this.TimelineContainer.Node, frame_history, this.CheckHandler); this.ThreadRows.push(row); this.ThreadRows.sort(function(a, b) { return b.Name.localeCompare(a.Name); }); } } TimelineWindow.prototype.DrawAllRows = function() { var time_range = this.TimeRange; var draw_text = this.Settings.IsPaused; for (var i in this.ThreadRows) { var thread_row = this.ThreadRows[i]; thread_row.SetVisibleFrames(time_range); thread_row.Draw(draw_text); } } function RowXOffset(self) { // Add sizing of the label // TODO: Use computed size return DOM.Node.GetPosition(self.TimelineContainer.Node)[0] + ROW_START_SIZE; } function RowWidth(self) { // Subtract sizing of the label // TODO: Use computed size return self.TimelineContainer.Size[0] - (ROW_START_SIZE + ROW_END_SIZE); } function OnUserResize(self, evt) { ResizeInternals(self); } function ResizeInternals(self) { // Resize controls var parent_size = self.Window.Size; self.TimelineContainer.SetPosition(BORDER, 10); self.TimelineContainer.SetSize(parent_size[0] - 2 * BORDER, parent_size[1] - 40); // Resize rows var row_width = RowWidth(self); for (var i in self.ThreadRows) { var row = self.ThreadRows[i]; row.SetSize(row_width); } // Adjust time range to new width self.TimeRange.SetPixelSpan(row_width); self.DrawAllRows(); } function OnMouseScroll(self, evt) { var mouse_state = new Mouse.State(evt); var scale = 1.11; if (mouse_state.WheelDelta > 0) scale = 1 / scale; // What time is the mouse hovering over? var x = mouse_state.Position[0] - RowXOffset(self); var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel; // Calculate start time relative to the mouse hover position var time_start_us = self.TimeRange.Start_us - time_us; // Scale and offset back to the hover time self.TimeRange.Set(time_start_us * scale + time_us, self.TimeRange.Span_us * scale); self.DrawAllRows(); // Prevent vertical scrolling on mouse-wheel DOM.Event.StopDefaultAction(evt); } function OnMouseDown(self, evt) { // Only manipulate the timelime when paused if (!self.Settings.IsPaused) return; self.MouseDown = true; self.LastMouseState = new Mouse.State(evt); self.TimelineMoved = false; DOM.Event.StopDefaultAction(evt); } function OnMouseUp(self, evt) { // Only manipulate the timelime when paused if (!self.Settings.IsPaused) return; var mouse_state = new Mouse.State(evt); self.MouseDown = false; if (!self.TimelineMoved) { // Search for the row being clicked and update its selection var row_node = DOM.Event.GetNode(evt); for (var i in self.ThreadRows) { var thread_row = self.ThreadRows[i]; if (thread_row.CanvasNode == row_node) { var select = thread_row.UpdateSelectedSample(mouse_state, RowXOffset(self)); // Call any selection handlers if (self.OnSelectedHandler) self.OnSelectedHandler(thread_row.Name, select); break; } } } } function OnMouseMove(self, evt) { // Only manipulate the timelime when paused if (!self.Settings.IsPaused) return; var mouse_state = new Mouse.State(evt); if (self.MouseDown) { // Get the time the mouse is over var x = mouse_state.Position[0] - RowXOffset(self); var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel; // Shift the visible time range with mouse movement var time_offset_us = (mouse_state.Position[0] - self.LastMouseState.Position[0]) / self.TimeRange.usPerPixel; if (time_offset_us) { self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us); self.DrawAllRows(); self.TimelineMoved = true; } } else { // Highlight any samples the mouse moves over var row_node = DOM.Event.GetNode(evt); for (var i in self.ThreadRows) { var thread_row = self.ThreadRows[i]; if (thread_row.CanvasNode == row_node) { var hover = thread_row.UpdateHoverSample(mouse_state, RowXOffset(self)); if (self.OnHoverHandler) self.OnHoverHandler(thread_row.Name, hover); } else { thread_row.SetHoverSample(null, 0); if (self.OnHoverHandler) self.OnHoverHandler(thread_row.Name, null); } } } self.LastMouseState = mouse_state; } return TimelineWindow; })();