Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "GraphWidget.h"
9#include <LibCore/Object.h>
10#include <LibGUI/Application.h>
11#include <LibGUI/Painter.h>
12#include <LibGfx/Font/Font.h>
13#include <LibGfx/Palette.h>
14#include <LibGfx/Path.h>
15#include <LibGfx/SystemTheme.h>
16
17REGISTER_WIDGET(SystemMonitor, GraphWidget)
18
19namespace SystemMonitor {
20
21GraphWidget::GraphWidget()
22{
23 REGISTER_BOOL_PROPERTY("stack_values", stack_values, set_stack_values);
24}
25
26void GraphWidget::set_stack_values(bool stack_values)
27{
28 m_stack_values = stack_values;
29 update();
30}
31
32void GraphWidget::add_value(Vector<u64, 1>&& value)
33{
34 m_values.enqueue(move(value));
35 update();
36}
37
38void GraphWidget::paint_event(GUI::PaintEvent& event)
39{
40 auto const& system_palette = GUI::Application::the()->palette();
41
42 GUI::Frame::paint_event(event);
43 GUI::Painter painter(*this);
44 painter.add_clip_rect(event.rect());
45 painter.add_clip_rect(frame_inner_rect());
46 painter.fill_rect(event.rect(), palette().base());
47
48 auto inner_rect = frame_inner_rect();
49 float scale = (float)inner_rect.height() / (float)m_max;
50
51 if (!m_values.is_empty()) {
52 // Draw one set of values at a time
53 for (size_t k = 0; k < m_value_format.size(); k++) {
54 auto const& format = m_value_format[k];
55 if (format.graph_color_role == ColorRole::Base) {
56 continue;
57 }
58 auto const& line_color = system_palette.color(format.graph_color_role);
59 auto const& background_color = line_color.with_alpha(0x7f);
60 m_calculated_points.clear_with_capacity();
61 for (size_t i = 0; i < m_values.size(); i++) {
62 int x = inner_rect.right() - (i * 2) + 1;
63 if (x < 0)
64 break;
65 auto const& current_values = m_values.at(m_values.size() - i - 1);
66 if (current_values.size() <= k) {
67 // Don't have a data point
68 m_calculated_points.append({ -1, -1 });
69 continue;
70 }
71 float value = current_values[k];
72 if (m_stack_values) {
73 for (size_t l = k + 1; l < current_values.size(); l++)
74 value += current_values[l];
75 }
76 float scaled_value = value * scale;
77 Gfx::IntPoint current_point { x, inner_rect.bottom() - (int)scaled_value };
78 m_calculated_points.append(current_point);
79 }
80 VERIFY(m_calculated_points.size() <= m_values.size());
81 if (format.graph_color_role != ColorRole::Base) {
82 // Fill the background for the area we have values for
83 Gfx::Path path;
84 size_t points_in_path = 0;
85 bool started_path = false;
86 Gfx::IntPoint const* current_point = nullptr;
87 Gfx::IntPoint const* first_point = nullptr;
88 auto check_fill_area = [&]() {
89 if (!started_path)
90 return;
91 if (points_in_path > 1) {
92 VERIFY(current_point);
93 VERIFY(first_point);
94 path.line_to({ current_point->x() - 1, inner_rect.bottom() + 1 });
95 path.line_to({ first_point->x() + 1, inner_rect.bottom() + 1 });
96 path.close();
97 painter.fill_path(path, background_color, Gfx::Painter::WindingRule::EvenOdd);
98 } else if (points_in_path == 1 && current_point) {
99 // Can't fill any area, we only have one data point.
100 // Just draw a vertical line as a "fill"...
101 painter.draw_line(*current_point, { current_point->x(), inner_rect.bottom() }, background_color);
102 }
103 path = {};
104 points_in_path = 0;
105 first_point = nullptr;
106 started_path = false;
107 };
108 for (size_t i = 0; i < m_calculated_points.size(); i++) {
109 current_point = &m_calculated_points[i];
110 if (current_point->x() < 0) {
111 check_fill_area();
112 continue;
113 }
114 if (!started_path) {
115 path.move_to({ current_point->x() + 1, current_point->y() });
116 points_in_path = 1;
117 first_point = current_point;
118 started_path = true;
119 } else {
120 path.line_to({ current_point->x(), current_point->y() });
121 points_in_path++;
122 }
123 }
124 check_fill_area();
125 }
126 if (format.graph_color_role != ColorRole::Base) {
127 // Draw the line for the data points we have
128 Gfx::IntPoint const* previous_point = nullptr;
129 for (size_t i = 0; i < m_calculated_points.size(); i++) {
130 auto const& current_point = m_calculated_points[i];
131 if (current_point.x() < 0) {
132 previous_point = nullptr;
133 continue;
134 }
135 if (previous_point)
136 painter.draw_line(*previous_point, current_point, line_color);
137 previous_point = ¤t_point;
138 }
139 }
140 }
141 }
142
143 if (!m_values.is_empty() && !m_value_format.is_empty()) {
144 auto const& current_values = m_values.last();
145 int y = 0;
146 for (size_t i = 0; i < min(m_value_format.size(), current_values.size()); i++) {
147 auto const& format = m_value_format[i];
148 auto const& graph_color = system_palette.color(format.graph_color_role);
149 if (!format.text_formatter)
150 continue;
151 auto constrain_rect = inner_rect.shrunken(8, 8);
152 auto text_rect = constrain_rect.translated(0, y).intersected(constrain_rect);
153 text_rect.set_height(font().pixel_size_rounded_up());
154 auto text = format.text_formatter(current_values[i]);
155 if (format.text_shadow_color != Color::Transparent)
156 painter.draw_text(text_rect.translated(1, 1), text, Gfx::TextAlignment::CenterRight, format.text_shadow_color);
157 painter.draw_text(text_rect, text, Gfx::TextAlignment::CenterRight, graph_color);
158 y += text_rect.height() + 4;
159 }
160 }
161}
162
163}