]>
Commit | Line | Data |
---|---|---|
1 | /* batteryviewer: Display battery metrics | |
2 | * Copyright (C) 2023 Scott Worley <scottworley@scottworley.com> | |
3 | * | |
4 | * This program is free software: you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation, version 3. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License | |
14 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | * | |
16 | * | |
17 | * | |
18 | * h/t https://ptomato.name/advanced-gtk-techniques/html/custom-container.html | |
19 | * for examples of how to make GTK widgets | |
20 | */ | |
21 | ||
22 | #include "chart.h" | |
23 | #include <float.h> | |
24 | #include <gtk/gtk.h> | |
25 | ||
26 | #define BV_CHART_PRIVATE(obj) \ | |
27 | (G_TYPE_INSTANCE_GET_PRIVATE((obj), BV_CHART_TYPE, BVChartPrivate)) | |
28 | ||
29 | typedef struct _BVChartPrivate BVChartPrivate; | |
30 | ||
31 | struct _BVChartPrivate { | |
32 | GArray *points; | |
33 | float minx, miny, maxx, maxy; | |
34 | }; | |
35 | ||
36 | struct BVChartPoint { | |
37 | float x, y; | |
38 | }; | |
39 | ||
40 | struct ScreenPoint { | |
41 | float x, y; | |
42 | }; | |
43 | ||
44 | G_DEFINE_TYPE_WITH_CODE(BVChart, bv_chart, GTK_TYPE_DRAWING_AREA, | |
45 | G_ADD_PRIVATE(BVChart)) | |
46 | ||
47 | typedef void (*cairo_draw_func)(cairo_t *cr, double x, double y); | |
48 | ||
49 | static struct ScreenPoint to_screen(BVChartPrivate *priv, | |
50 | GtkAllocation *allocation, | |
51 | struct BVChartPoint *p) { | |
52 | int margin = 3; | |
53 | float xscale = (allocation->width - 2 * margin) / (priv->maxx - priv->minx); | |
54 | float yscale = (allocation->height - 2 * margin) / (priv->miny - priv->maxy); | |
55 | struct ScreenPoint screen_p = { | |
56 | .x = xscale * (p->x - priv->minx) + margin, | |
57 | .y = yscale * (p->y - priv->maxy) + margin, | |
58 | }; | |
59 | return screen_p; | |
60 | } | |
61 | ||
62 | static void draw_data(BVChartPrivate *priv, cairo_t *cr, | |
63 | GtkAllocation *allocation) { | |
64 | cairo_set_source_rgb(cr, 0.0, 0.0, 1.0); | |
65 | float gap_threshold = 3.0; | |
66 | float prevx = 0.0; | |
67 | for (guint i = 0; i < priv->points->len; i++) { | |
68 | struct BVChartPoint *p = | |
69 | &g_array_index(priv->points, struct BVChartPoint, i); | |
70 | gboolean is_gap = p->x - prevx > gap_threshold; | |
71 | cairo_draw_func f = is_gap ? cairo_move_to : cairo_line_to; | |
72 | struct ScreenPoint screen_p = to_screen(priv, allocation, p); | |
73 | f(cr, screen_p.x, screen_p.y); | |
74 | prevx = p->x; | |
75 | } | |
76 | cairo_stroke(cr); | |
77 | } | |
78 | ||
79 | static void draw_axis(BVChartPrivate *priv, cairo_t *cr, | |
80 | GtkAllocation *allocation) { | |
81 | if (priv->miny < 0.0 && priv->maxy > 0.0) { | |
82 | cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); | |
83 | struct BVChartPoint p = {.x = priv->minx, .y = 0.0}; | |
84 | struct ScreenPoint screen_p = to_screen(priv, allocation, &p); | |
85 | cairo_move_to(cr, screen_p.x, screen_p.y); | |
86 | p.x = priv->maxx; | |
87 | screen_p = to_screen(priv, allocation, &p); | |
88 | cairo_line_to(cr, screen_p.x, screen_p.y); | |
89 | cairo_stroke(cr); | |
90 | } | |
91 | } | |
92 | ||
93 | static gboolean bv_chart_draw(GtkWidget *widget, cairo_t *cr) { | |
94 | BVChart *chart = BV_CHART(widget); | |
95 | BVChartPrivate *priv = bv_chart_get_instance_private(chart); | |
96 | ||
97 | cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); | |
98 | cairo_paint(cr); | |
99 | ||
100 | if (priv->points->len < 2) | |
101 | return TRUE; | |
102 | ||
103 | GtkAllocation allocation; | |
104 | gtk_widget_get_allocation(widget, &allocation); | |
105 | ||
106 | draw_data(priv, cr, &allocation); | |
107 | draw_axis(priv, cr, &allocation); | |
108 | ||
109 | return TRUE; | |
110 | } | |
111 | ||
112 | static void bv_chart_class_init(BVChartClass *klass) { | |
113 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); | |
114 | widget_class->draw = bv_chart_draw; | |
115 | } | |
116 | ||
117 | static void bv_chart_init(BVChart *chart) { | |
118 | gtk_widget_set_has_window(GTK_WIDGET(chart), FALSE); | |
119 | BVChartPrivate *priv = bv_chart_get_instance_private(chart); | |
120 | gboolean zero_terminated = FALSE; | |
121 | gboolean clear_ = FALSE; | |
122 | priv->points = | |
123 | g_array_new(zero_terminated, clear_, sizeof(struct BVChartPoint)); | |
124 | priv->minx = FLT_MAX; | |
125 | priv->miny = FLT_MAX; | |
126 | priv->maxx = FLT_MIN; | |
127 | priv->maxy = FLT_MIN; | |
128 | } | |
129 | ||
130 | GtkWidget *bv_chart_new() { | |
131 | return GTK_WIDGET(g_object_new(bv_chart_get_type(), NULL)); | |
132 | } | |
133 | ||
134 | void bv_chart_add_point(BVChart *chart, float x, float y) { | |
135 | BVChartPrivate *priv = bv_chart_get_instance_private(chart); | |
136 | if (x < priv->minx) | |
137 | priv->minx = x; | |
138 | if (y < priv->miny) | |
139 | priv->miny = y; | |
140 | if (x > priv->maxx) | |
141 | priv->maxx = x; | |
142 | if (y > priv->maxy) | |
143 | priv->maxy = y; | |
144 | struct BVChartPoint p = {.x = x, .y = y}; | |
145 | g_array_append_val(priv->points, p); | |
146 | } |