Ver código fonte

Profiler: Add fixed track headers to the timeline view

The architecture here is a little bit convoluted. I ended up making a
new container widget (TimelineContainer) that works similarly to
GUI::ScrollableContainerWidget but has two subwidgets (a fixed header
that only scrolls vertically, and the timeline view that scrolls on
both axes.)

It would be nice to generalize this mechanism eventually and move it
back into LibGUI, but for now let's go with a special widget for
Profiler so we can continue iterating on the GUI. :^)
Andreas Kling 4 anos atrás
pai
commit
fb6d236ba2

+ 11 - 9
Userland/DevTools/Profiler/CMakeLists.txt

@@ -1,15 +1,17 @@
 set(SOURCES
-    DisassemblyModel.cpp
-    main.cpp
-    IndividualSampleModel.cpp
-    Process.cpp
-    ProcessPickerWidget.cpp
-    Profile.cpp
-    ProfileModel.cpp
-        TimelineTrack.cpp
+        DisassemblyModel.cpp
+        main.cpp
+        IndividualSampleModel.cpp
+        Process.cpp
+        ProcessPickerWidget.cpp
+        Profile.cpp
+        ProfileModel.cpp
         SamplesModel.cpp
+        TimelineContainer.cpp
+        TimelineHeader.cpp
+        TimelineTrack.cpp
         TimelineView.cpp
-)
+        )
 
 serenity_app(Profiler ICON app-profiler)
 target_link_libraries(Profiler LibGUI LibDesktop LibX86)

+ 61 - 0
Userland/DevTools/Profiler/TimelineContainer.cpp

@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "TimelineContainer.h"
+#include "TimelineView.h"
+#include <LibGUI/Layout.h>
+
+namespace Profiler {
+
+TimelineContainer::TimelineContainer(GUI::Widget& header_container, TimelineView& timeline_view)
+{
+    m_header_container = header_container;
+    m_timeline_view = timeline_view;
+    add_child(header_container);
+    add_child(timeline_view);
+    header_container.move_to_back();
+    timeline_view.move_to_back();
+}
+
+TimelineContainer::~TimelineContainer()
+{
+}
+
+void TimelineContainer::did_scroll()
+{
+    AbstractScrollableWidget::did_scroll();
+    update_widget_positions();
+}
+
+void TimelineContainer::update_widget_positions()
+{
+    m_header_container->move_to(0, -vertical_scrollbar().value());
+    m_timeline_view->move_to(m_header_container->width() + -horizontal_scrollbar().value(), -vertical_scrollbar().value());
+}
+
+void TimelineContainer::update_widget_sizes()
+{
+    {
+        m_timeline_view->do_layout();
+        auto preferred_size = m_timeline_view->layout()->preferred_size();
+        m_timeline_view->resize(preferred_size);
+        set_content_size(preferred_size);
+    }
+
+    {
+        m_header_container->do_layout();
+        auto preferred_size = m_header_container->layout()->preferred_size();
+        m_header_container->resize(preferred_size);
+    }
+}
+
+void TimelineContainer::resize_event(GUI::ResizeEvent& event)
+{
+    AbstractScrollableWidget::resize_event(event);
+    update_widget_sizes();
+}
+
+}

+ 35 - 0
Userland/DevTools/Profiler/TimelineContainer.h

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibGUI/AbstractScrollableWidget.h>
+
+namespace Profiler {
+
+class TimelineView;
+
+class TimelineContainer : public GUI::AbstractScrollableWidget {
+    C_OBJECT(TimelineContainer);
+
+public:
+    virtual ~TimelineContainer();
+
+protected:
+    virtual void did_scroll() override;
+    virtual void resize_event(GUI::ResizeEvent&) override;
+
+private:
+    void update_widget_sizes();
+    void update_widget_positions();
+
+    TimelineContainer(GUI::Widget& header_container, TimelineView&);
+
+    RefPtr<TimelineView> m_timeline_view;
+    RefPtr<GUI::Widget> m_header_container;
+};
+
+}

+ 54 - 0
Userland/DevTools/Profiler/TimelineHeader.cpp

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "TimelineHeader.h"
+#include "Process.h"
+#include <AK/LexicalPath.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Painter.h>
+
+namespace Profiler {
+
+TimelineHeader::TimelineHeader(Process const& process)
+    : m_process(process)
+{
+    set_frame_shape(Gfx::FrameShape::Panel);
+    set_frame_shadow(Gfx::FrameShadow::Raised);
+    set_fixed_size(200, 40);
+
+    m_icon = GUI::FileIconProvider::icon_for_executable(m_process.executable).bitmap_for_size(32);
+    m_text = String::formatted("{} ({})", LexicalPath(m_process.executable).basename(), m_process.pid);
+}
+
+TimelineHeader::~TimelineHeader()
+{
+}
+
+void TimelineHeader::paint_event(GUI::PaintEvent& event)
+{
+    GUI::Frame::paint_event(event);
+    GUI::Painter painter(*this);
+    painter.add_clip_rect(event.rect());
+
+    Gfx::IntRect icon_rect { frame_thickness() + 2, 0, 32, 32 };
+    icon_rect.center_vertically_within(frame_inner_rect());
+
+    if (m_icon)
+        painter.blit(icon_rect.location(), *m_icon, m_icon->rect());
+
+    Gfx::IntRect text_rect {
+        icon_rect.right() + 6,
+        icon_rect.y(),
+        width() - 32,
+        32
+    };
+    text_rect.center_vertically_within(frame_inner_rect());
+
+    painter.draw_text(text_rect, m_text, Gfx::TextAlignment::CenterLeft);
+}
+
+}

+ 31 - 0
Userland/DevTools/Profiler/TimelineHeader.h

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibGUI/Frame.h>
+
+namespace Profiler {
+
+class Process;
+
+class TimelineHeader final : public GUI::Frame {
+    C_OBJECT(TimelineHeader);
+
+public:
+    virtual ~TimelineHeader();
+
+private:
+    TimelineHeader(Process const&);
+
+    virtual void paint_event(GUI::PaintEvent&) override;
+
+    Process const& m_process;
+    RefPtr<Gfx::Bitmap> m_icon;
+    String m_text;
+};
+
+}

+ 0 - 9
Userland/DevTools/Profiler/TimelineTrack.cpp

@@ -74,15 +74,6 @@ void TimelineTrack::paint_event(GUI::PaintEvent& event)
     int select_hover_x = (int)((float)(normalized_hover_time - start_of_trace) * column_width);
     painter.fill_rect({ select_start_x, frame_thickness(), select_end_x - select_start_x, height() - frame_thickness() * 2 }, Color(0, 0, 0, 60));
     painter.fill_rect({ select_hover_x, frame_thickness(), 1, height() - frame_thickness() * 2 }, Color::NamedColor::Black);
-
-    auto text = String::formatted("{} ({})", m_process.executable, m_process.pid);
-    Gfx::IntRect text_rect {
-        frame_thickness() + 3,
-        frame_thickness() + 3,
-        font().width(text),
-        font().glyph_height()
-    };
-    painter.draw_text(text_rect, text, font());
 }
 
 u64 TimelineTrack::timestamp_at_x(int x) const

+ 9 - 3
Userland/DevTools/Profiler/main.cpp

@@ -7,6 +7,8 @@
 #include "IndividualSampleModel.h"
 #include "ProcessPickerWidget.h"
 #include "Profile.h"
+#include "TimelineContainer.h"
+#include "TimelineHeader.h"
 #include "TimelineTrack.h"
 #include "TimelineView.h"
 #include <LibCore/ArgsParser.h>
@@ -25,7 +27,6 @@
 #include <LibGUI/MessageBox.h>
 #include <LibGUI/Model.h>
 #include <LibGUI/ProcessChooser.h>
-#include <LibGUI/ScrollableContainerWidget.h>
 #include <LibGUI/Splitter.h>
 #include <LibGUI/Statusbar.h>
 #include <LibGUI/TabWidget.h>
@@ -91,6 +92,11 @@ int main(int argc, char** argv)
     main_widget.set_fill_with_background_color(true);
     main_widget.set_layout<GUI::VerticalBoxLayout>();
 
+    auto timeline_header_container = GUI::Widget::construct();
+    timeline_header_container->set_layout<GUI::VerticalBoxLayout>();
+    timeline_header_container->set_fill_with_background_color(true);
+    timeline_header_container->set_shrink_to_fit(true);
+
     auto timeline_view = TimelineView::construct();
     for (auto& process : profile->processes()) {
         size_t event_count = 0;
@@ -100,11 +106,11 @@ int main(int argc, char** argv)
         }
         if (!event_count)
             continue;
+        timeline_header_container->add<TimelineHeader>(process);
         timeline_view->add<TimelineTrack>(*timeline_view, *profile, process);
     }
 
-    auto& scrollable_container = main_widget.add<GUI::ScrollableContainerWidget>();
-    scrollable_container.set_widget(timeline_view.ptr());
+    [[maybe_unused]] auto& timeline_container = main_widget.add<TimelineContainer>(*timeline_header_container, *timeline_view);
 
     main_widget.add<ProcessPickerWidget>(*profile);