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