44#include < cfloat>
55#include < cmath>
66#include < numeric>
7+ #include < optional>
78#include < string>
89
910#include " xprof/frontend/app/components/trace_viewer_v2/color/color_generator.h"
@@ -56,15 +57,17 @@ void Timeline::Draw() {
5657 ImGui::SetNextWindowPos (viewport->Pos );
5758 ImGui::SetNextWindowSize (viewport->Size );
5859 ImGui::SetNextWindowViewport (viewport->ID );
60+
5961 ImGui::PushStyleVar (ImGuiStyleVar_WindowRounding, 0 .f );
62+ ImGui::PushStyleVar (ImGuiStyleVar_CellPadding, ImVec2 (0 .0f , 0 .0f ));
63+ ImGui::PushStyleVar (ImGuiStyleVar_ItemSpacing, ImVec2 (0 .0f , 0 .0f ));
64+
6065 ImGui::Begin (" Timeline viewer" , nullptr , kImGuiWindowFlags );
6166
6267 if (timeline_data_.groups .empty ()) {
6368 DrawLoadingIndicator (viewport);
6469 }
6570
66- ImGui::PushStyleVar (ImGuiStyleVar_ItemSpacing, ImVec2 (0 .0f , 0 .0f ));
67-
6871 const Pixel timeline_width =
6972 ImGui::GetContentRegionAvail ().x - label_width_ - kTimelinePaddingRight ;
7073 const double px_per_time_unit_val = px_per_time_unit (timeline_width);
@@ -97,71 +100,14 @@ void Timeline::Draw() {
97100 ImGui::Unindent ((group.nesting_level + 1 ) * kIndentSize );
98101
99102 ImGui::TableNextColumn ();
100- const int start_level = group.start_level ;
101- int end_level = (group_index + 1 < timeline_data_.groups .size ())
102- ? timeline_data_.groups [group_index + 1 ].start_level
103- // If this is the last group, the end level is the total
104- // number of levels.
105- : timeline_data_.events_by_level .size ();
106- // Ensure end_level is not less than start_level, to avoid negative height.
107- end_level = std::max (start_level, end_level);
108-
109- // Calculate group height. Ensure a minimum height of one level to prevent
110- // ImGui::BeginChild from auto-resizing, even if a group contains no levels.
111- // This is important for parent groups (e.g., a process) that might not
112- // contain any event levels directly.
113- // TODO: b/453676716 - Add tests for group height calculation.
114- const Pixel group_height = std::max (1 , end_level - start_level) *
115- (kEventHeight + kEventPaddingBottom );
116- // Groups might have the same name. We add the index of the group to the ID
117- // to ensure each ImGui::BeginChild call has a unique ID, otherwise ImGui
118- // might ignore later calls with the same name.
119- const std::string timeline_child_id =
120- absl::StrCat (" TimelineChild_" , group.name , " _" , group_index);
121-
122- if (ImGui::BeginChild (timeline_child_id.c_str (), ImVec2 (0 , group_height),
123- ImGuiChildFlags_None, kLaneFlags )) {
124- const ImVec2 pos = ImGui::GetCursorScreenPos ();
125- const ImVec2 max = ImGui::GetContentRegionMax ();
126-
127- for (int level = start_level; level < end_level; ++level) {
128- // This is a sanity check to ensure the level is within the bounds of
129- // events_by_level.
130- if (level < timeline_data_.events_by_level .size ()) {
131- // TODO: b/453676716 - Add boundary test cases for this function.
132- DrawEventsForLevel (timeline_data_.events_by_level [level],
133- px_per_time_unit_val,
134- /* level_in_group=*/ level - start_level, pos, max);
135- }
136- }
137- }
138- ImGui::EndChild ();
139-
140- if (group_index < timeline_data_.groups .size () - 1 ) {
141- ImDrawList* draw_list = ImGui::GetWindowDrawList ();
142- float line_y =
143- ImGui::GetItemRectMax ().y + ImGui::GetStyle ().CellPadding .y ;
144- draw_list->AddLine (ImVec2 (viewport->Pos .x + label_width_ + 15 , line_y),
145- ImVec2 (viewport->Pos .x + viewport->Size .x , line_y),
146- kLightGrayColor );
147- }
103+ DrawGroup (group_index, px_per_time_unit_val);
148104 }
149105
150106 ImGui::EndTable ();
151107
152- // If an event was selected, and the user clicks on an empty area
153- // (i.e., not on any event), deselect the event.
154- if (selected_event_index_ != -1 && ImGui::IsMouseClicked (0 ) &&
155- ImGui::IsWindowHovered (ImGuiHoveredFlags_ChildWindows) &&
156- !event_clicked_this_frame_) {
157- selected_event_index_ = -1 ;
158-
159- EventData event_data;
160- event_data[std::string (kEventSelectedIndex )] = -1 ;
161- event_data[std::string (kEventSelectedName )] = std::string (" " );
108+ DrawSelectedTimeRange (timeline_width, px_per_time_unit_val);
162109
163- event_callback_ (kEventSelected , event_data);
164- }
110+ HandleEventDeselection ();
165111
166112 // Handle continuous keyboard and mouse wheel input for timeline navigation.
167113 // These functions are called every frame to ensure smooth and responsive
@@ -173,8 +119,9 @@ void Timeline::Draw() {
173119
174120 ImGui::EndChild ();
175121 ImGui::PopStyleVar (); // ItemSpacing
122+ ImGui::PopStyleVar (); // CellPadding
176123 ImGui::PopStyleVar (); // WindowRounding
177- ImGui::End ();
124+ ImGui::End (); // Timeline viewer
178125}
179126
180127EventRect Timeline::CalculateEventRect (Microseconds start, Microseconds end,
@@ -388,6 +335,87 @@ double Timeline::px_per_time_unit(Pixel timeline_width) const {
388335 }
389336}
390337
338+ // Draws the timeline ruler. This includes the main horizontal line,
339+ // vertical tick marks indicating time intervals, and their corresponding time
340+ // labels.
341+ void Timeline::DrawRuler (Pixel timeline_width) {
342+ if (ImGui::BeginTable (" Ruler" , 2 , kImGuiTableFlags )) {
343+ ImGui::TableSetupColumn (" Labels" , ImGuiTableColumnFlags_WidthFixed,
344+ label_width_);
345+ ImGui::TableSetupColumn (" Timeline" , ImGuiTableColumnFlags_WidthStretch);
346+ ImGui::TableNextRow ();
347+ ImGui::TableSetBgColor (ImGuiTableBgTarget_RowBg0,
348+ ImGui::GetColorU32 (ImGuiCol_WindowBg));
349+ ImGui::TableNextColumn ();
350+ ImGui::TableNextColumn ();
351+
352+ const ImVec2 pos = ImGui::GetCursorScreenPos ();
353+ ImDrawList* const draw_list = ImGui::GetWindowDrawList ();
354+
355+ const double px_per_time_unit_val = px_per_time_unit (timeline_width);
356+ if (px_per_time_unit_val > 0 ) {
357+ // Draw horizontal line
358+ const Pixel line_y = pos.y + kRulerHeight ;
359+ draw_list->AddLine (ImVec2 (pos.x , line_y),
360+ ImVec2 (pos.x + timeline_width, line_y),
361+ kRulerLineColor );
362+
363+ const Microseconds min_time_interval =
364+ kMinTickDistancePx / px_per_time_unit_val;
365+ const Microseconds tick_interval =
366+ CalculateNiceInterval (min_time_interval);
367+ const Pixel major_tick_dist_px = tick_interval * px_per_time_unit_val;
368+
369+ const Microseconds view_start = visible_range ().start ();
370+ const Microseconds trace_start = data_time_range_.start ();
371+
372+ const Microseconds view_start_relative = view_start - trace_start;
373+ const Microseconds first_tick_time_relative =
374+ std::floor (view_start_relative / tick_interval) * tick_interval;
375+
376+ const Pixel minor_tick_dist_px =
377+ major_tick_dist_px / static_cast <float >(kMinorTickDivisions );
378+
379+ Microseconds t_relative = first_tick_time_relative;
380+ Pixel x =
381+ TimeToScreenX (t_relative + trace_start, pos.x , px_per_time_unit_val);
382+
383+ for (;; t_relative += tick_interval, x += major_tick_dist_px) {
384+ if (x > pos.x + timeline_width + kRulerScreenBuffer ) {
385+ break ;
386+ }
387+
388+ // Draw major tick.
389+ if (x >= pos.x - kRulerScreenBuffer ) {
390+ draw_list->AddLine (ImVec2 (x, line_y - kRulerTickHeight ),
391+ ImVec2 (x, line_y), kRulerLineColor );
392+
393+ const std::string text = FormatTime (t_relative);
394+ draw_list->AddText (ImVec2 (x + kRulerTextPadding , pos.y ),
395+ kRulerTextColor , text.c_str ());
396+ }
397+
398+ // Draw minor ticks for the current interval.
399+ for (int i = 1 ; i < kMinorTickDivisions ; ++i) {
400+ const Pixel minor_x = x + i * minor_tick_dist_px;
401+ if (minor_x > pos.x + timeline_width + kRulerScreenBuffer ) {
402+ break ;
403+ }
404+ if (minor_x >= pos.x - kRulerScreenBuffer ) {
405+ draw_list->AddLine (
406+ ImVec2 (minor_x, line_y - kRulerTickHeight / 2 .0f ),
407+ ImVec2 (minor_x, line_y), kRulerLineColor );
408+ }
409+ }
410+ }
411+ }
412+
413+ // Reserve space for the ruler
414+ ImGui::Dummy (ImVec2 (0 .0f , kRulerHeight + ImGui::GetStyle ().CellPadding .y ));
415+ ImGui::EndTable ();
416+ }
417+ }
418+
391419void Timeline::DrawEventName (absl::string_view event_name,
392420 const EventRect& event_rect,
393421 ImDrawList* absl_nonnull draw_list) const {
@@ -442,6 +470,20 @@ void Timeline::DrawEvent(int event_index, const EventRect& rect,
442470 // ImGuiMouseButton enum. We check if the left mouse button was clicked.
443471 if (ImGui::IsMouseClicked (0 )) {
444472 event_clicked_this_frame_ = true ;
473+
474+ if (ImGui::GetIO ().KeyShift ) {
475+ const Microseconds start =
476+ timeline_data_.entry_start_times [event_index];
477+ const Microseconds end =
478+ start + timeline_data_.entry_total_times [event_index];
479+ TimeRange event_range (start, end);
480+ if (selected_time_range_ == event_range) {
481+ selected_time_range_ = std::nullopt ;
482+ } else {
483+ selected_time_range_ = event_range;
484+ }
485+ }
486+
445487 if (selected_event_index_ != event_index) {
446488 selected_event_index_ = event_index;
447489
@@ -492,41 +534,6 @@ void Timeline::DrawEventsForLevel(absl::Span<const int> event_indices,
492534 }
493535}
494536
495- void Timeline::HandleKeyboard () {
496- const ImGuiIO& io = ImGui::GetIO ();
497-
498- // Pan left
499- if (ImGui::IsKeyDown (ImGuiKey_A)) {
500- float multiplier = GetSpeedMultiplier (io, ImGuiKey_A);
501- Pan (-kPanningSpeed * io.DeltaTime * multiplier);
502- }
503- // Pan right
504- if (ImGui::IsKeyDown (ImGuiKey_D)) {
505- float multiplier = GetSpeedMultiplier (io, ImGuiKey_D);
506- Pan (kPanningSpeed * io.DeltaTime * multiplier);
507- }
508-
509- // Scroll up
510- if (ImGui::IsKeyDown (ImGuiKey_UpArrow)) {
511- Scroll (-kScrollSpeed * io.DeltaTime );
512- }
513- // Scroll down
514- if (ImGui::IsKeyDown (ImGuiKey_DownArrow)) {
515- Scroll (kScrollSpeed * io.DeltaTime );
516- }
517-
518- // Zoom in
519- if (ImGui::IsKeyDown (ImGuiKey_W)) {
520- float multiplier = GetSpeedMultiplier (io, ImGuiKey_W);
521- Zoom (1 .0f - kZoomSpeed * io.DeltaTime * multiplier);
522- }
523- // Zoom out
524- if (ImGui::IsKeyDown (ImGuiKey_S)) {
525- float multiplier = GetSpeedMultiplier (io, ImGuiKey_S);
526- Zoom (1 .0f + kZoomSpeed * io.DeltaTime * multiplier);
527- }
528- }
529-
530537// Draws the timeline ruler. This includes the main horizontal line,
531538// vertical tick marks indicating time intervals, and their corresponding time
532539// labels.
@@ -610,6 +617,72 @@ void Timeline::DrawRuler(Pixel timeline_width, Pixel viewport_bottom) {
610617 }
611618}
612619
620+ void Timeline::DrawSelectedTimeRange (Pixel timeline_width,
621+ double px_per_time_unit_val) {
622+ if (!selected_time_range_) return ;
623+
624+ const ImVec2 table_rect_min = ImGui::GetItemRectMin ();
625+ const ImVec2 table_rect_max = ImGui::GetItemRectMax ();
626+ const Pixel timeline_x_start = table_rect_min.x + label_width_;
627+
628+ const Pixel time_range_x1 = TimeToScreenX (
629+ selected_time_range_->start (), timeline_x_start, px_per_time_unit_val);
630+ const Pixel time_range_x2 = TimeToScreenX (
631+ selected_time_range_->end (), timeline_x_start, px_per_time_unit_val);
632+ const Pixel clipped_x1 = std::max (time_range_x1, timeline_x_start);
633+ const Pixel clipped_x2 =
634+ std::min (time_range_x2, timeline_x_start + timeline_width);
635+
636+ if (clipped_x2 > clipped_x1) {
637+ // Use the foreground draw list to render over all other timeline content.
638+ ImDrawList* const draw_list = ImGui::GetForegroundDrawList ();
639+ draw_list->AddRectFilled (ImVec2 (clipped_x1, table_rect_min.y ),
640+ ImVec2 (clipped_x2, table_rect_max.y ),
641+ kSelectedTimeRangeColor );
642+ draw_list->AddLine (ImVec2 (clipped_x1, table_rect_min.y ),
643+ ImVec2 (clipped_x1, table_rect_max.y ),
644+ kSelectedTimeRangeBorderColor );
645+ draw_list->AddLine (ImVec2 (clipped_x2, table_rect_min.y ),
646+ ImVec2 (clipped_x2, table_rect_max.y ),
647+ kSelectedTimeRangeBorderColor );
648+ }
649+ }
650+
651+ void Timeline::HandleKeyboard () {
652+ const ImGuiIO& io = ImGui::GetIO ();
653+
654+ // Pan left
655+ if (ImGui::IsKeyDown (ImGuiKey_A)) {
656+ float multiplier = GetSpeedMultiplier (io, ImGuiKey_A);
657+ Pan (-kPanningSpeed * io.DeltaTime * multiplier);
658+ }
659+ // Pan right
660+ if (ImGui::IsKeyDown (ImGuiKey_D)) {
661+ float multiplier = GetSpeedMultiplier (io, ImGuiKey_D);
662+ Pan (kPanningSpeed * io.DeltaTime * multiplier);
663+ }
664+
665+ // Scroll up
666+ if (ImGui::IsKeyDown (ImGuiKey_UpArrow)) {
667+ Scroll (-kScrollSpeed * io.DeltaTime );
668+ }
669+ // Scroll down
670+ if (ImGui::IsKeyDown (ImGuiKey_DownArrow)) {
671+ Scroll (kScrollSpeed * io.DeltaTime );
672+ }
673+
674+ // Zoom in
675+ if (ImGui::IsKeyDown (ImGuiKey_W)) {
676+ float multiplier = GetSpeedMultiplier (io, ImGuiKey_W);
677+ Zoom (1 .0f - kZoomSpeed * io.DeltaTime * multiplier);
678+ }
679+ // Zoom out
680+ if (ImGui::IsKeyDown (ImGuiKey_S)) {
681+ float multiplier = GetSpeedMultiplier (io, ImGuiKey_S);
682+ Zoom (1 .0f + kZoomSpeed * io.DeltaTime * multiplier);
683+ }
684+ }
685+
613686void Timeline::HandleWheel () {
614687 const ImGuiIO& io = ImGui::GetIO ();
615688
@@ -636,4 +709,20 @@ void Timeline::HandleWheel() {
636709 }
637710}
638711
712+ void Timeline::HandleEventDeselection () {
713+ // If an event was selected, and the user clicks on an empty area
714+ // (i.e., not on any event), deselect the event.
715+ if (selected_event_index_ != -1 && ImGui::IsMouseClicked (0 ) &&
716+ ImGui::IsWindowHovered (ImGuiHoveredFlags_ChildWindows) &&
717+ !event_clicked_this_frame_) {
718+ selected_event_index_ = -1 ;
719+
720+ EventData event_data;
721+ event_data[std::string (kEventSelectedIndex )] = -1 ;
722+ event_data[std::string (kEventSelectedName )] = std::string (" " );
723+
724+ event_callback_ (kEventSelected , event_data);
725+ }
726+ }
727+
639728} // namespace traceviewer
0 commit comments