From fb6d236ba2511f2f4a8b31adf844951215f50aeb Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 6 May 2021 19:50:18 +0200 Subject: [PATCH] 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. :^) --- Userland/DevTools/Profiler/CMakeLists.txt | 20 +++--- .../DevTools/Profiler/TimelineContainer.cpp | 61 +++++++++++++++++++ .../DevTools/Profiler/TimelineContainer.h | 35 +++++++++++ Userland/DevTools/Profiler/TimelineHeader.cpp | 54 ++++++++++++++++ Userland/DevTools/Profiler/TimelineHeader.h | 31 ++++++++++ Userland/DevTools/Profiler/TimelineTrack.cpp | 9 --- Userland/DevTools/Profiler/main.cpp | 12 +++- 7 files changed, 201 insertions(+), 21 deletions(-) create mode 100644 Userland/DevTools/Profiler/TimelineContainer.cpp create mode 100644 Userland/DevTools/Profiler/TimelineContainer.h create mode 100644 Userland/DevTools/Profiler/TimelineHeader.cpp create mode 100644 Userland/DevTools/Profiler/TimelineHeader.h diff --git a/Userland/DevTools/Profiler/CMakeLists.txt b/Userland/DevTools/Profiler/CMakeLists.txt index 539fdb7269..c5406b8480 100644 --- a/Userland/DevTools/Profiler/CMakeLists.txt +++ b/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) diff --git a/Userland/DevTools/Profiler/TimelineContainer.cpp b/Userland/DevTools/Profiler/TimelineContainer.cpp new file mode 100644 index 0000000000..0810f30679 --- /dev/null +++ b/Userland/DevTools/Profiler/TimelineContainer.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "TimelineContainer.h" +#include "TimelineView.h" +#include + +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(); +} + +} diff --git a/Userland/DevTools/Profiler/TimelineContainer.h b/Userland/DevTools/Profiler/TimelineContainer.h new file mode 100644 index 0000000000..9760e25ef3 --- /dev/null +++ b/Userland/DevTools/Profiler/TimelineContainer.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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 m_timeline_view; + RefPtr m_header_container; +}; + +} diff --git a/Userland/DevTools/Profiler/TimelineHeader.cpp b/Userland/DevTools/Profiler/TimelineHeader.cpp new file mode 100644 index 0000000000..0e1b13a8cf --- /dev/null +++ b/Userland/DevTools/Profiler/TimelineHeader.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "TimelineHeader.h" +#include "Process.h" +#include +#include +#include +#include + +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); +} + +} diff --git a/Userland/DevTools/Profiler/TimelineHeader.h b/Userland/DevTools/Profiler/TimelineHeader.h new file mode 100644 index 0000000000..ba307aa7c0 --- /dev/null +++ b/Userland/DevTools/Profiler/TimelineHeader.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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 m_icon; + String m_text; +}; + +} diff --git a/Userland/DevTools/Profiler/TimelineTrack.cpp b/Userland/DevTools/Profiler/TimelineTrack.cpp index 96d7b181ae..74f4d09e6f 100644 --- a/Userland/DevTools/Profiler/TimelineTrack.cpp +++ b/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 diff --git a/Userland/DevTools/Profiler/main.cpp b/Userland/DevTools/Profiler/main.cpp index b908c9175b..a49640e1ab 100644 --- a/Userland/DevTools/Profiler/main.cpp +++ b/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 @@ -25,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -91,6 +92,11 @@ int main(int argc, char** argv) main_widget.set_fill_with_background_color(true); main_widget.set_layout(); + auto timeline_header_container = GUI::Widget::construct(); + timeline_header_container->set_layout(); + 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(process); timeline_view->add(*timeline_view, *profile, process); } - auto& scrollable_container = main_widget.add(); - scrollable_container.set_widget(timeline_view.ptr()); + [[maybe_unused]] auto& timeline_container = main_widget.add(*timeline_header_container, *timeline_view); main_widget.add(*profile);