1const std = @import("std");2const mem = std.mem;3const unicode = std.unicode;45const fcft = @import("fcft");6const pixman = @import("pixman");7const time = @cImport(@cInclude("time.h"));89const Buffer = @import("Buffer.zig");10const Bar = @import("Bar.zig");11const Tag = @import("Tags.zig").Tag;1213const state = &@import("root").state;1415pub const RenderFn = fn (*Bar) anyerror!void;1617pub fn toUtf8(gpa: mem.Allocator, bytes: []const u8) ![]u32 {18 const utf8 = try unicode.Utf8View.init(bytes);19 var iter = utf8.iterator();2021 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 }2627 return runes.toOwnedSlice(gpa);28}2930pub fn renderTags(bar: *Bar) !void {31 const surface = bar.tags.surface;32 const tags = bar.monitor.tags.tags;3334 const buffers = &bar.tags.buffers;35 const shm = state.wayland.shm.?;3637 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;4142 for (&tags, 0..) |*tag, i| {43 const offset: i16 = @intCast(bar.height * i);44 try renderTag(buffer.pix.?, tag, bar.height, offset);45 }4647 // Separator tag to visually separate last focused tag from48 // 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);5152 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}5758fn 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);6162 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 }7172 return x;73}7475pub fn renderTitle(bar: *Bar, title: ?[]const u8) !void {76 const surface = bar.title.surface;77 const shm = state.wayland.shm.?;7879 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 }8788 // calculate width89 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 };100101 // set subsurface offset102 const x_offset = bar.tags_width;103 const y_offset = 0;104 bar.title.subsurface.setPosition(x_offset, y_offset);105106 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;110111 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);119120 if (runes) |r| {121 const font = state.config.font;122 const run = try font.rasterizeTextRunUtf32(r, .default);123 defer run.destroy();124125 // calculate maximum amount of glyphs that can be displayed126 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 }138139 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 abbreviated143 _ = try renderRun(x, buffer, color, bar, bar.abbrev_run.glyphs, bar.abbrev_run.count);144 }145 }146147 surface.setBufferScale(bar.monitor.scale);148 surface.damageBuffer(0, 0, width, bar.height);149 surface.attach(buffer.buffer, 0, 0);150}151152pub fn resetText(bar: *Bar) !void {153 const surface = bar.text.surface;154 const shm = state.wayland.shm.?;155156 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;160161 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);168169 surface.setBufferScale(bar.monitor.scale);170 surface.damageBuffer(0, 0, bar.text_width, bar.height);171 surface.attach(buffer.buffer, 0, 0);172}173174pub fn renderText(bar: *Bar, text: []const u8) !void {175 const surface = bar.text.surface;176 const shm = state.wayland.shm.?;177178 // utf8 encoding179 const runes = try toUtf8(state.gpa, text);180 defer state.gpa.free(runes);181182 // rasterize183 const font = state.config.font;184 const run = try font.rasterizeTextRunUtf32(runes, .default);185 defer run.destroy();186187 // compute total width188 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 }193194 // set subsurface offset195 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);199200 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;204205 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);210211 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 }221222 surface.setBufferScale(bar.monitor.scale);223 surface.damageBuffer(0, 0, width, bar.height);224 surface.attach(buffer.buffer, 0, 0);225226 // render title again if text width changed227 if (width != bar.text_width) {228 bar.text_width = width;229230 if (state.wayland.river_seat) |seat| {231 seat.mtx.lock();232 defer seat.mtx.unlock();233234 try renderTitle(bar, seat.window_title);235 bar.title.surface.commit();236 bar.background.surface.commit();237 }238 }239}240241fn 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);252253 if (tag.occupied) {254 const font_height: u16 = @intCast(state.config.font.height);255256 // Constants taken from dwm-6.3 drawbar function.257 const boxs: i16 = @intCast(font_height / 9);258 const boxw: u16 = font_height / 6 + 2;259260 const box = pixman.Rectangle16{261 .x = offset + boxs,262 .y = boxs,263 .width = boxw,264 .height = boxw,265 };266267 const box_color = if (tag.focused) blk: {268 break :blk &state.config.normalBgColor;269 } else blk: {270 break :blk tag.fgColor();271 };272273 _ = pixman.Image.fillRectangles(.over, pix, box_color, 1, &[_]pixman.Rectangle16{box});274 if (!tag.focused) {275 const border = 1; // size of the border276 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 };282283 const inner_color = tag.bgColor();284 _ = pixman.Image.fillRectangles(.over, pix, inner_color, 1, &[_]pixman.Rectangle16{inner});285 }286 }287288 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}