1const std = @import("std");
2const mem = std.mem;
3const unicode = std.unicode;
4
5const fcft = @import("fcft");
6const pixman = @import("pixman");
7const time = @cImport(@cInclude("time.h"));
8
9const Buffer = @import("Buffer.zig");
10const Bar = @import("Bar.zig");
11const Tag = @import("Tags.zig").Tag;
12
13const state = &@import("root").state;
14
15pub const RenderFn = fn (*Bar) anyerror!void;
16
17pub fn toUtf8(gpa: mem.Allocator, bytes: []const u8) ![]u32 {
18 const utf8 = try unicode.Utf8View.init(bytes);
19 var iter = utf8.iterator();
20
21 var runes = try std.ArrayList(u32).initCapacity(gpa, bytes.len);
22 var i: usize = 0;
23 while (iter.nextCodepoint()) |rune| : (i += 1) {
24 runes.appendAssumeCapacity(rune);
25 }
26
27 return runes.toOwnedSlice();
28}
29
30pub fn renderTags(bar: *Bar) !void {
31 const surface = bar.tags.surface;
32 const tags = bar.monitor.tags.tags;
33
34 const buffers = &bar.tags.buffers;
35 const shm = state.wayland.shm.?;
36
37 const width = bar.height * @as(u16, tags.len + 1);
38 const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
39 if (buffer.buffer == null) return;
40 buffer.busy = true;
41
42 for (&tags, 0..) |*tag, i| {
43 const offset: i16 = @intCast(bar.height * i);
44 try renderTag(buffer.pix.?, tag, bar.height, offset);
45 }
46
47 // Separator tag to visually separate last focused tag from
48 // focused window title (both use the same background color).
49 const offset: i16 = @intCast(bar.height * tags.len);
50 try renderTag(buffer.pix.?, &Tag{ .label = '|' }, bar.height, offset);
51
52 bar.tags_width = width;
53 surface.setBufferScale(bar.monitor.scale);
54 surface.damageBuffer(0, 0, width, bar.height);
55 surface.attach(buffer.buffer, 0, 0);
56}
57
58fn renderRun(start: i32, buffer: *Buffer, image: *pixman.Image, bar: *Bar, glyphs: [*]*const fcft.Glyph, count: usize) !i32 {
59 const font_height: u32 = @intCast(state.config.font.height);
60 const y_offset: i32 = @intCast((bar.height - font_height) / 2);
61
62 var i: usize = 0;
63 var x: i32 = start;
64 while (i < count) : (i += 1) {
65 const glyph = glyphs[i];
66 x += @intCast(glyph.x);
67 const y = (state.config.font.ascent - @as(i32, @intCast(glyph.y))) + y_offset;
68 pixman.Image.composite32(.over, image, glyph.pix, buffer.pix.?, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
69 x += glyph.advance.x - @as(i32, @intCast(glyph.x));
70 }
71
72 return x;
73}
74
75pub fn renderTitle(bar: *Bar, title: ?[]const u8) !void {
76 const surface = bar.title.surface;
77 const shm = state.wayland.shm.?;
78
79 var runes: ?[]u32 = null;
80 if (title) |t| {
81 if (t.len > 0)
82 runes = try toUtf8(state.gpa, t);
83 }
84 defer {
85 if (runes) |r| state.gpa.free(r);
86 }
87
88 // calculate width
89 const title_start = bar.tags_width;
90 const text_start = if (bar.text_width == 0) blk: {
91 break :blk 0;
92 } else blk: {
93 break :blk bar.width - bar.text_width - bar.text_padding;
94 };
95 const width: u16 = if (text_start > 0) blk: {
96 break :blk @intCast(text_start - title_start - bar.text_padding);
97 } else blk: {
98 break :blk bar.width - title_start;
99 };
100
101 // set subsurface offset
102 const x_offset = bar.tags_width;
103 const y_offset = 0;
104 bar.title.subsurface.setPosition(x_offset, y_offset);
105
106 const buffers = &bar.title.buffers;
107 const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
108 if (buffer.buffer == null) return;
109 buffer.busy = true;
110
111 var bg_color = state.config.normalBgColor;
112 if (title) |t| {
113 if (t.len > 0) bg_color = state.config.focusBgColor;
114 }
115 const bg_area = [_]pixman.Rectangle16{
116 .{ .x = 0, .y = 0, .width = width, .height = bar.height },
117 };
118 _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);
119
120 if (runes) |r| {
121 const font = state.config.font;
122 const run = try font.rasterizeTextRunUtf32(r, .default);
123 defer run.destroy();
124
125 // calculate maximum amount of glyphs that can be displayed
126 var max_x: i32 = bar.text_padding;
127 var max_glyphs: u16 = 0;
128 var i: usize = 0;
129 while (i < run.count) : (i += 1) {
130 const glyph = run.glyphs[i];
131 max_x += @intCast(glyph.x);
132 if (max_x >= width - (2 * bar.text_padding) - bar.abbrev_width) {
133 break;
134 }
135 max_x += glyph.advance.x - @as(i32, @intCast(glyph.x));
136 max_glyphs += 1;
137 }
138
139 var x: i32 = bar.text_padding;
140 const color = pixman.Image.createSolidFill(&state.config.focusFgColor).?;
141 x += try renderRun(bar.text_padding, buffer, color, bar, run.glyphs, max_glyphs);
142 if (run.count > max_glyphs) { // if abbreviated
143 _ = try renderRun(x, buffer, color, bar, bar.abbrev_run.glyphs, bar.abbrev_run.count);
144 }
145 }
146
147 surface.setBufferScale(bar.monitor.scale);
148 surface.damageBuffer(0, 0, width, bar.height);
149 surface.attach(buffer.buffer, 0, 0);
150}
151
152pub fn resetText(bar: *Bar) !void {
153 const surface = bar.text.surface;
154 const shm = state.wayland.shm.?;
155
156 const buffers = &bar.text.buffers;
157 const buffer = try Buffer.nextBuffer(buffers, shm, bar.text_width, bar.height);
158 if (buffer.buffer == null) return;
159 buffer.busy = true;
160
161 const text_to_bottom: u16 =
162 @intCast(state.config.font.height + bar.text_padding);
163 const bg_area = [_]pixman.Rectangle16{
164 .{ .x = 0, .y = 0, .width = bar.text_width, .height = text_to_bottom },
165 };
166 var bg_color = state.config.normalBgColor;
167 _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);
168
169 surface.setBufferScale(bar.monitor.scale);
170 surface.damageBuffer(0, 0, bar.text_width, bar.height);
171 surface.attach(buffer.buffer, 0, 0);
172}
173
174pub fn renderText(bar: *Bar, text: []const u8) !void {
175 const surface = bar.text.surface;
176 const shm = state.wayland.shm.?;
177
178 // utf8 encoding
179 const runes = try toUtf8(state.gpa, text);
180 defer state.gpa.free(runes);
181
182 // rasterize
183 const font = state.config.font;
184 const run = try font.rasterizeTextRunUtf32(runes, .default);
185 defer run.destroy();
186
187 // compute total width
188 var i: usize = 0;
189 var width: u16 = 0;
190 while (i < run.count) : (i += 1) {
191 width += @intCast(run.glyphs[i].advance.x);
192 }
193
194 // set subsurface offset
195 const font_height: u32 = @intCast(state.config.font.height);
196 const x_offset: i32 = @intCast(bar.width - width - bar.text_padding);
197 const y_offset: i32 = @intCast(@divFloor(bar.height - font_height, 2));
198 bar.text.subsurface.setPosition(x_offset, y_offset);
199
200 const buffers = &bar.text.buffers;
201 const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
202 if (buffer.buffer == null) return;
203 buffer.busy = true;
204
205 const bg_area = [_]pixman.Rectangle16{
206 .{ .x = 0, .y = 0, .width = width, .height = bar.height },
207 };
208 const bg_color = mem.zeroes(pixman.Color);
209 _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);
210
211 var x: i32 = 0;
212 i = 0;
213 const color = pixman.Image.createSolidFill(&state.config.normalFgColor).?;
214 while (i < run.count) : (i += 1) {
215 const glyph = run.glyphs[i];
216 x += @intCast(glyph.x);
217 const y = state.config.font.ascent - @as(i32, @intCast(glyph.y));
218 pixman.Image.composite32(.over, color, glyph.pix, buffer.pix.?, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
219 x += glyph.advance.x - @as(i32, @intCast(glyph.x));
220 }
221
222 surface.setBufferScale(bar.monitor.scale);
223 surface.damageBuffer(0, 0, width, bar.height);
224 surface.attach(buffer.buffer, 0, 0);
225
226 // render title again if text width changed
227 if (width != bar.text_width) {
228 bar.text_width = width;
229
230 if (state.wayland.river_seat) |seat| {
231 seat.mtx.lock();
232 defer seat.mtx.unlock();
233
234 try renderTitle(bar, seat.window_title);
235 bar.title.surface.commit();
236 bar.background.surface.commit();
237 }
238 }
239}
240
241fn renderTag(
242 pix: *pixman.Image,
243 tag: *const Tag,
244 size: u16,
245 offset: i16,
246) !void {
247 const outer = [_]pixman.Rectangle16{
248 .{ .x = offset, .y = 0, .width = size, .height = size },
249 };
250 const outer_color = tag.bgColor();
251 _ = pixman.Image.fillRectangles(.over, pix, outer_color, 1, &outer);
252
253 if (tag.occupied) {
254 const font_height: u16 = @intCast(state.config.font.height);
255
256 // Constants taken from dwm-6.3 drawbar function.
257 const boxs: i16 = @intCast(font_height / 9);
258 const boxw: u16 = font_height / 6 + 2;
259
260 const box = pixman.Rectangle16{
261 .x = offset + boxs,
262 .y = boxs,
263 .width = boxw,
264 .height = boxw,
265 };
266
267 const box_color = if (tag.focused) blk: {
268 break :blk &state.config.normalBgColor;
269 } else blk: {
270 break :blk tag.fgColor();
271 };
272
273 _ = pixman.Image.fillRectangles(.over, pix, box_color, 1, &[_]pixman.Rectangle16{box});
274 if (!tag.focused) {
275 const border = 1; // size of the border
276 const inner = pixman.Rectangle16{
277 .x = box.x + border,
278 .y = box.y + border,
279 .width = box.width - (2 * border),
280 .height = box.height - (2 * border),
281 };
282
283 const inner_color = tag.bgColor();
284 _ = pixman.Image.fillRectangles(.over, pix, inner_color, 1, &[_]pixman.Rectangle16{inner});
285 }
286 }
287
288 const glyph_color = tag.fgColor();
289 const font = state.config.font;
290 const char = pixman.Image.createSolidFill(glyph_color).?;
291 const glyph = try font.rasterizeCharUtf32(tag.label, .default);
292 const x = offset + @divFloor(size - glyph.width, 2);
293 const y = @divFloor(size - glyph.height, 2);
294 pixman.Image.composite32(.over, char, glyph.pix, pix, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
295}