diff options
Diffstat (limited to 'swaynag/render.c')
-rw-r--r-- | swaynag/render.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/swaynag/render.c b/swaynag/render.c new file mode 100644 index 00000000..766409e4 --- /dev/null +++ b/swaynag/render.c | |||
@@ -0,0 +1,308 @@ | |||
1 | #include <stdint.h> | ||
2 | #include "cairo.h" | ||
3 | #include "log.h" | ||
4 | #include "pango.h" | ||
5 | #include "pool-buffer.h" | ||
6 | #include "swaynag/swaynag.h" | ||
7 | #include "swaynag/types.h" | ||
8 | #include "wlr-layer-shell-unstable-v1-client-protocol.h" | ||
9 | |||
10 | static uint32_t render_message(cairo_t *cairo, struct swaynag *swaynag) { | ||
11 | uint32_t height = swaynag->height * swaynag->scale; | ||
12 | height -= swaynag->type->bar_border_thickness * swaynag->scale; | ||
13 | |||
14 | int text_width, text_height; | ||
15 | get_text_size(cairo, swaynag->type->font, &text_width, &text_height, | ||
16 | swaynag->scale, true, "%s", swaynag->message); | ||
17 | |||
18 | int padding = swaynag->type->message_padding * swaynag->scale; | ||
19 | |||
20 | uint32_t ideal_height = text_height + padding * 2; | ||
21 | uint32_t ideal_surface_height = ideal_height / swaynag->scale; | ||
22 | if (swaynag->height < ideal_surface_height) { | ||
23 | return ideal_surface_height; | ||
24 | } | ||
25 | |||
26 | cairo_set_source_u32(cairo, swaynag->type->text); | ||
27 | cairo_move_to(cairo, padding, (int)(ideal_height - text_height) / 2); | ||
28 | pango_printf(cairo, swaynag->type->font, swaynag->scale, false, | ||
29 | "%s", swaynag->message); | ||
30 | |||
31 | return ideal_surface_height; | ||
32 | } | ||
33 | |||
34 | static void render_details_scroll_button(cairo_t *cairo, | ||
35 | struct swaynag *swaynag, struct swaynag_button *button) { | ||
36 | int text_width, text_height; | ||
37 | get_text_size(cairo, swaynag->type->font, &text_width, &text_height, | ||
38 | swaynag->scale, true, "%s", button->text); | ||
39 | |||
40 | int border = swaynag->type->button_border_thickness * swaynag->scale; | ||
41 | int padding = swaynag->type->button_padding * swaynag->scale; | ||
42 | |||
43 | cairo_set_source_u32(cairo, swaynag->type->border); | ||
44 | cairo_rectangle(cairo, button->x, button->y, | ||
45 | button->width, button->height); | ||
46 | cairo_fill(cairo); | ||
47 | |||
48 | cairo_set_source_u32(cairo, swaynag->type->button_background); | ||
49 | cairo_rectangle(cairo, button->x + border, button->y + border, | ||
50 | button->width - (border * 2), button->height - (border * 2)); | ||
51 | cairo_fill(cairo); | ||
52 | |||
53 | cairo_set_source_u32(cairo, swaynag->type->text); | ||
54 | cairo_move_to(cairo, button->x + border + padding, | ||
55 | button->y + border + (button->height - text_height) / 2); | ||
56 | pango_printf(cairo, swaynag->type->font, swaynag->scale, true, | ||
57 | "%s", button->text); | ||
58 | } | ||
59 | |||
60 | static int get_detailed_scroll_button_width(cairo_t *cairo, | ||
61 | struct swaynag *swaynag) { | ||
62 | int up_width, down_width, temp_height; | ||
63 | get_text_size(cairo, swaynag->type->font, &up_width, &temp_height, | ||
64 | swaynag->scale, true, | ||
65 | "%s", swaynag->details.button_up.text); | ||
66 | get_text_size(cairo, swaynag->type->font, &down_width, &temp_height, | ||
67 | swaynag->scale, true, | ||
68 | "%s", swaynag->details.button_down.text); | ||
69 | |||
70 | int text_width = up_width > down_width ? up_width : down_width; | ||
71 | int border = swaynag->type->button_border_thickness * swaynag->scale; | ||
72 | int padding = swaynag->type->button_padding * swaynag->scale; | ||
73 | |||
74 | return text_width + border * 2 + padding * 2; | ||
75 | } | ||
76 | |||
77 | static uint32_t render_detailed(cairo_t *cairo, struct swaynag *swaynag, | ||
78 | uint32_t y) { | ||
79 | uint32_t width = swaynag->width * swaynag->scale; | ||
80 | uint32_t height = swaynag->height * swaynag->scale; | ||
81 | height -= swaynag->type->bar_border_thickness * swaynag->scale; | ||
82 | |||
83 | int border = swaynag->type->details_border_thickness * swaynag->scale; | ||
84 | int padding = swaynag->type->message_padding * swaynag->scale; | ||
85 | int decor = padding + border; | ||
86 | |||
87 | swaynag->details.x = decor; | ||
88 | swaynag->details.y = y * swaynag->scale + decor; | ||
89 | swaynag->details.width = width - decor * 2; | ||
90 | |||
91 | PangoLayout *layout = get_pango_layout(cairo, swaynag->type->font, | ||
92 | swaynag->details.message, swaynag->scale, false); | ||
93 | pango_layout_set_width(layout, | ||
94 | (swaynag->details.width - padding * 2) * PANGO_SCALE); | ||
95 | pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); | ||
96 | pango_layout_set_single_paragraph_mode(layout, false); | ||
97 | pango_cairo_update_layout(cairo, layout); | ||
98 | swaynag->details.total_lines = pango_layout_get_line_count(layout); | ||
99 | |||
100 | PangoLayoutLine *line; | ||
101 | line = pango_layout_get_line_readonly(layout, swaynag->details.offset); | ||
102 | gint offset = line->start_index; | ||
103 | const char *text = pango_layout_get_text(layout); | ||
104 | pango_layout_set_text(layout, text + offset, strlen(text) - offset); | ||
105 | |||
106 | int text_width, text_height; | ||
107 | pango_cairo_update_layout(cairo, layout); | ||
108 | pango_layout_get_pixel_size(layout, &text_width, &text_height); | ||
109 | |||
110 | bool show_buttons = swaynag->details.offset > 0; | ||
111 | int button_width = get_detailed_scroll_button_width(cairo, swaynag); | ||
112 | if (show_buttons) { | ||
113 | swaynag->details.width -= button_width; | ||
114 | pango_layout_set_width(layout, | ||
115 | (swaynag->details.width - padding * 2) * PANGO_SCALE); | ||
116 | } | ||
117 | |||
118 | uint32_t ideal_height; | ||
119 | do { | ||
120 | ideal_height = swaynag->details.y + text_height + decor + padding * 2; | ||
121 | if (ideal_height > SWAYNAG_MAX_HEIGHT) { | ||
122 | ideal_height = SWAYNAG_MAX_HEIGHT; | ||
123 | |||
124 | if (!show_buttons) { | ||
125 | show_buttons = true; | ||
126 | swaynag->details.width -= button_width; | ||
127 | pango_layout_set_width(layout, | ||
128 | (swaynag->details.width - padding * 2) * PANGO_SCALE); | ||
129 | } | ||
130 | } | ||
131 | |||
132 | swaynag->details.height = ideal_height - swaynag->details.y - decor; | ||
133 | pango_layout_set_height(layout, | ||
134 | (swaynag->details.height - padding * 2) * PANGO_SCALE); | ||
135 | pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); | ||
136 | pango_cairo_update_layout(cairo, layout); | ||
137 | pango_layout_get_pixel_size(layout, &text_width, &text_height); | ||
138 | } while (text_height != (swaynag->details.height - padding * 2)); | ||
139 | |||
140 | swaynag->details.visible_lines = pango_layout_get_line_count(layout); | ||
141 | |||
142 | if (show_buttons) { | ||
143 | swaynag->details.button_up.x = | ||
144 | swaynag->details.x + swaynag->details.width; | ||
145 | swaynag->details.button_up.y = swaynag->details.y; | ||
146 | swaynag->details.button_up.width = button_width; | ||
147 | swaynag->details.button_up.height = swaynag->details.height / 2; | ||
148 | render_details_scroll_button(cairo, swaynag, | ||
149 | &swaynag->details.button_up); | ||
150 | |||
151 | swaynag->details.button_down.x = | ||
152 | swaynag->details.x + swaynag->details.width; | ||
153 | swaynag->details.button_down.y = | ||
154 | swaynag->details.button_up.y + swaynag->details.button_up.height; | ||
155 | swaynag->details.button_down.width = button_width; | ||
156 | swaynag->details.button_down.height = swaynag->details.height / 2; | ||
157 | render_details_scroll_button(cairo, swaynag, | ||
158 | &swaynag->details.button_down); | ||
159 | } | ||
160 | |||
161 | cairo_set_source_u32(cairo, swaynag->type->border); | ||
162 | cairo_rectangle(cairo, swaynag->details.x, swaynag->details.y, | ||
163 | swaynag->details.width, swaynag->details.height); | ||
164 | cairo_fill(cairo); | ||
165 | |||
166 | cairo_move_to(cairo, swaynag->details.x + padding, | ||
167 | swaynag->details.y + padding); | ||
168 | cairo_set_source_u32(cairo, swaynag->type->text); | ||
169 | pango_cairo_show_layout(cairo, layout); | ||
170 | g_object_unref(layout); | ||
171 | |||
172 | return ideal_height / swaynag->scale; | ||
173 | } | ||
174 | |||
175 | static uint32_t render_button(cairo_t *cairo, struct swaynag *swaynag, | ||
176 | int button_index, int *x) { | ||
177 | uint32_t height = swaynag->height * swaynag->scale; | ||
178 | height -= swaynag->type->bar_border_thickness * swaynag->scale; | ||
179 | struct swaynag_button *button = swaynag->buttons->items[button_index]; | ||
180 | |||
181 | int text_width, text_height; | ||
182 | get_text_size(cairo, swaynag->type->font, &text_width, &text_height, | ||
183 | swaynag->scale, true, "%s", button->text); | ||
184 | |||
185 | int border = swaynag->type->button_border_thickness * swaynag->scale; | ||
186 | int padding = swaynag->type->button_padding * swaynag->scale; | ||
187 | |||
188 | uint32_t ideal_height = text_height + padding * 2 + border * 2; | ||
189 | uint32_t ideal_surface_height = ideal_height / swaynag->scale; | ||
190 | if (swaynag->height < ideal_surface_height) { | ||
191 | return ideal_surface_height; | ||
192 | } | ||
193 | |||
194 | button->x = *x - border - text_width - padding * 2; | ||
195 | button->y = (int)(ideal_height - text_height) / 2 - padding; | ||
196 | button->width = text_width + padding * 2; | ||
197 | button->height = text_height + padding * 2; | ||
198 | |||
199 | cairo_set_source_u32(cairo, swaynag->type->border); | ||
200 | cairo_rectangle(cairo, button->x - border, button->y - border, | ||
201 | button->width + border * 2, button->height + border * 2); | ||
202 | cairo_fill(cairo); | ||
203 | |||
204 | cairo_set_source_u32(cairo, swaynag->type->button_background); | ||
205 | cairo_rectangle(cairo, button->x, button->y, | ||
206 | button->width, button->height); | ||
207 | cairo_fill(cairo); | ||
208 | |||
209 | cairo_set_source_u32(cairo, swaynag->type->text); | ||
210 | cairo_move_to(cairo, button->x + padding, button->y + padding); | ||
211 | pango_printf(cairo, swaynag->type->font, swaynag->scale, true, | ||
212 | "%s", button->text); | ||
213 | |||
214 | *x = button->x - border; | ||
215 | |||
216 | return ideal_surface_height; | ||
217 | } | ||
218 | |||
219 | static uint32_t render_to_cairo(cairo_t *cairo, struct swaynag *swaynag) { | ||
220 | uint32_t max_height = 0; | ||
221 | |||
222 | cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); | ||
223 | cairo_set_source_u32(cairo, swaynag->type->background); | ||
224 | cairo_paint(cairo); | ||
225 | |||
226 | uint32_t h = render_message(cairo, swaynag); | ||
227 | max_height = h > max_height ? h : max_height; | ||
228 | |||
229 | int x = swaynag->width - swaynag->type->button_margin_right; | ||
230 | x *= swaynag->scale; | ||
231 | for (int i = 0; i < swaynag->buttons->length; i++) { | ||
232 | h = render_button(cairo, swaynag, i, &x); | ||
233 | max_height = h > max_height ? h : max_height; | ||
234 | x -= swaynag->type->button_gap * swaynag->scale; | ||
235 | if (i == 0) { | ||
236 | x -= swaynag->type->button_gap_close * swaynag->scale; | ||
237 | } | ||
238 | } | ||
239 | |||
240 | if (swaynag->details.visible) { | ||
241 | h = render_detailed(cairo, swaynag, max_height); | ||
242 | max_height = h > max_height ? h : max_height; | ||
243 | } | ||
244 | |||
245 | int border = swaynag->type->bar_border_thickness * swaynag->scale; | ||
246 | if (max_height > swaynag->height) { | ||
247 | max_height += border; | ||
248 | } | ||
249 | cairo_set_source_u32(cairo, swaynag->type->border_bottom); | ||
250 | cairo_rectangle(cairo, 0, | ||
251 | swaynag->height * swaynag->scale - border, | ||
252 | swaynag->width * swaynag->scale, | ||
253 | border); | ||
254 | cairo_fill(cairo); | ||
255 | |||
256 | return max_height; | ||
257 | } | ||
258 | |||
259 | void render_frame(struct swaynag *swaynag) { | ||
260 | if (!swaynag->run_display) { | ||
261 | return; | ||
262 | } | ||
263 | |||
264 | cairo_surface_t *recorder = cairo_recording_surface_create( | ||
265 | CAIRO_CONTENT_COLOR_ALPHA, NULL); | ||
266 | cairo_t *cairo = cairo_create(recorder); | ||
267 | cairo_save(cairo); | ||
268 | cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); | ||
269 | cairo_paint(cairo); | ||
270 | cairo_restore(cairo); | ||
271 | uint32_t height = render_to_cairo(cairo, swaynag); | ||
272 | if (height != swaynag->height) { | ||
273 | zwlr_layer_surface_v1_set_size(swaynag->layer_surface, 0, height); | ||
274 | zwlr_layer_surface_v1_set_exclusive_zone(swaynag->layer_surface, | ||
275 | height); | ||
276 | wl_surface_commit(swaynag->surface); | ||
277 | wl_display_roundtrip(swaynag->display); | ||
278 | } else { | ||
279 | swaynag->current_buffer = get_next_buffer(swaynag->shm, | ||
280 | swaynag->buffers, | ||
281 | swaynag->width * swaynag->scale, | ||
282 | swaynag->height * swaynag->scale); | ||
283 | if (!swaynag->current_buffer) { | ||
284 | wlr_log(WLR_DEBUG, "Failed to get buffer. Skipping frame."); | ||
285 | goto cleanup; | ||
286 | } | ||
287 | |||
288 | cairo_t *shm = swaynag->current_buffer->cairo; | ||
289 | cairo_save(shm); | ||
290 | cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR); | ||
291 | cairo_paint(shm); | ||
292 | cairo_restore(shm); | ||
293 | cairo_set_source_surface(shm, recorder, 0.0, 0.0); | ||
294 | cairo_paint(shm); | ||
295 | |||
296 | wl_surface_set_buffer_scale(swaynag->surface, swaynag->scale); | ||
297 | wl_surface_attach(swaynag->surface, | ||
298 | swaynag->current_buffer->buffer, 0, 0); | ||
299 | wl_surface_damage(swaynag->surface, 0, 0, | ||
300 | swaynag->width, swaynag->height); | ||
301 | wl_surface_commit(swaynag->surface); | ||
302 | wl_display_roundtrip(swaynag->display); | ||
303 | } | ||
304 | |||
305 | cleanup: | ||
306 | cairo_surface_destroy(recorder); | ||
307 | cairo_destroy(cairo); | ||
308 | } | ||