stevee

My wayland statusbar
git clone git://gtms.dev/stevee
Log | Files | Refs | README | LICENSE

render.zig (7359B)


      1 const std = @import("std");
      2 const mem = std.mem;
      3 
      4 const fcft = @import("fcft");
      5 const pixman = @import("pixman");
      6 const time = @cImport(@cInclude("time.h"));
      7 
      8 const Buffer = @import("Buffer.zig");
      9 const Bar = @import("Bar.zig");
     10 const Tag = @import("Tags.zig").Tag;
     11 const utils = @import("utils.zig");
     12 
     13 const Backlight = @import("modules/Backlight.zig");
     14 const Battery = @import("modules/Battery.zig");
     15 const Pulse = @import("modules/Pulse.zig");
     16 
     17 const state = &@import("root").state;
     18 
     19 pub const RenderFn = fn (*Bar) anyerror!void;
     20 
     21 pub fn renderTags(bar: *Bar) !void {
     22     const surface = bar.tags.surface;
     23     const tags = bar.monitor.tags.tags;
     24 
     25     const buffers = &bar.tags.buffers;
     26     const shm = state.wayland.shm.?;
     27 
     28     const width = bar.height * @as(u16, @intCast(bar.monitor.tags.tags.len));
     29     const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
     30     if (buffer.buffer == null) return;
     31     buffer.busy = true;
     32 
     33     for (&tags, 0..) |*tag, i| {
     34         const offset = bar.height * i;
     35         try renderTag(buffer.pix.?, tag, bar.height, @intCast(offset));
     36     }
     37 
     38     surface.setBufferScale(1);
     39     surface.damageBuffer(0, 0, width, bar.height);
     40     surface.attach(buffer.buffer, 0, 0);
     41 }
     42 
     43 pub fn renderClock(bar: *Bar) !void {
     44     const surface = bar.clock.surface;
     45     const shm = state.wayland.shm.?;
     46 
     47     // utf8 datetime
     48     const str = try formatDatetime();
     49     defer state.gpa.free(str);
     50     const runes = try utils.toUtf8(state.gpa, str);
     51     defer state.gpa.free(runes);
     52 
     53     // resterize
     54     const font = state.config.font;
     55     const run = try font.rasterizeTextRunUtf32(runes, .default);
     56     defer run.destroy();
     57 
     58     // compute total width
     59     var i: usize = 0;
     60     var width: u16 = 0;
     61     while (i < run.count) : (i += 1) {
     62         width += @as(u16, @intCast(run.glyphs[i].advance.x));
     63     }
     64 
     65     // set subsurface offset
     66     const font_height = @as(u32, @intCast(font.height));
     67     const x_offset = @as(i32, @intCast((bar.width - width) / 2));
     68     const y_offset = @as(i32, @intCast((bar.height - font_height) / 2));
     69     bar.clock.subsurface.setPosition(x_offset, y_offset);
     70 
     71     const buffers = &bar.clock.buffers;
     72     const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
     73     if (buffer.buffer == null) return;
     74     buffer.busy = true;
     75 
     76     const bg_area = [_]pixman.Rectangle16{
     77         .{ .x = 0, .y = 0, .width = width, .height = bar.height },
     78     };
     79     const bg_color = mem.zeroes(pixman.Color);
     80     _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);
     81 
     82     var x: i32 = 0;
     83     i = 0;
     84     const color = pixman.Image.createSolidFill(&state.config.foregroundColor).?;
     85     while (i < run.count) : (i += 1) {
     86         const glyph = run.glyphs[i];
     87         x += @as(i32, @intCast(glyph.x));
     88         const y = state.config.font.ascent - @as(i32, @intCast(glyph.y));
     89         pixman.Image.composite32(.over, color, glyph.pix, buffer.pix.?, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
     90         x += glyph.advance.x - @as(i32, @intCast(glyph.x));
     91     }
     92 
     93     surface.setBufferScale(1);
     94     surface.damageBuffer(0, 0, width, bar.height);
     95     surface.attach(buffer.buffer, 0, 0);
     96 }
     97 
     98 pub fn renderModules(bar: *Bar) !void {
     99     const surface = bar.modules.surface;
    100     const shm = state.wayland.shm.?;
    101 
    102     // compose string
    103     var string = std.ArrayList(u8).init(state.gpa);
    104     defer string.deinit();
    105 
    106     const writer = string.writer();
    107     for (state.modules.order.items) |tag| {
    108         try writer.print(" | ", .{});
    109         switch (tag) {
    110             .backlight => try state.modules.backlight.?.print(writer),
    111             .battery => try state.modules.battery.?.print(writer),
    112             .pulse => try state.modules.pulse.?.print(writer),
    113         }
    114     }
    115     try writer.print(" ", .{});
    116 
    117     // ut8 encoding
    118     const runes = try utils.toUtf8(state.gpa, string.items);
    119     defer state.gpa.free(runes);
    120 
    121     // rasterize
    122     const font = state.config.font;
    123     const run = try font.rasterizeTextRunUtf32(runes, .default);
    124     defer run.destroy();
    125 
    126     // compute total width
    127     var i: usize = 0;
    128     var width: u16 = 0;
    129     while (i < run.count) : (i += 1) {
    130         width += @as(u16, @intCast(run.glyphs[i].advance.x));
    131     }
    132 
    133     // set subsurface offset
    134     const font_height = @as(u32, @intCast(state.config.font.height));
    135     const x_offset = @as(i32, @intCast(bar.width - width));
    136     const y_offset = @as(i32, @intCast(@divFloor(bar.height - font_height, 2)));
    137     bar.modules.subsurface.setPosition(x_offset, y_offset);
    138 
    139     const buffers = &bar.modules.buffers;
    140     const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
    141     if (buffer.buffer == null) return;
    142     buffer.busy = true;
    143 
    144     const bg_area = [_]pixman.Rectangle16{
    145         .{ .x = 0, .y = 0, .width = width, .height = bar.height },
    146     };
    147     const bg_color = mem.zeroes(pixman.Color);
    148     _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);
    149 
    150     var x: i32 = 0;
    151     i = 0;
    152     const color = pixman.Image.createSolidFill(&state.config.foregroundColor).?;
    153     while (i < run.count) : (i += 1) {
    154         const glyph = run.glyphs[i];
    155         x += @as(i32, @intCast(glyph.x));
    156         const y = state.config.font.ascent - @as(i32, @intCast(glyph.y));
    157         pixman.Image.composite32(.over, color, glyph.pix, buffer.pix.?, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
    158         x += glyph.advance.x - @as(i32, @intCast(glyph.x));
    159     }
    160 
    161     surface.setBufferScale(1);
    162     surface.damageBuffer(0, 0, width, bar.height);
    163     surface.attach(buffer.buffer, 0, 0);
    164 }
    165 
    166 fn renderTag(
    167     pix: *pixman.Image,
    168     tag: *const Tag,
    169     height: u16,
    170     offset: i16,
    171 ) !void {
    172     const size = height;
    173 
    174     const outer = [_]pixman.Rectangle16{
    175         .{ .x = offset, .y = 0, .width = size, .height = size },
    176     };
    177     const outer_color = if (tag.focused or tag.occupied) blk: {
    178         break :blk &state.config.foregroundColor;
    179     } else blk: {
    180         break :blk &state.config.backgroundColor;
    181     };
    182     _ = pixman.Image.fillRectangles(.over, pix, outer_color, 1, &outer);
    183 
    184     const border = state.config.border;
    185     const inner = [_]pixman.Rectangle16{
    186         .{
    187             .x = offset + border,
    188             .y = border,
    189             .width = size - 2 * border,
    190             .height = size - 2 * border,
    191         },
    192     };
    193     const inner_color = &state.config.backgroundColor;
    194     if (!tag.focused and tag.occupied) {
    195         _ = pixman.Image.fillRectangles(.over, pix, inner_color, 1, &inner);
    196     }
    197 
    198     const glyph_color = if (tag.focused) blk: {
    199         break :blk &state.config.backgroundColor;
    200     } else blk: {
    201         break :blk &state.config.foregroundColor;
    202     };
    203     const font = state.config.font;
    204     const char = pixman.Image.createSolidFill(glyph_color).?;
    205     const glyph = try font.rasterizeCharUtf32(tag.label, .default);
    206     const x = offset + @divFloor(size - glyph.width, 2);
    207     const y = @divFloor(size - glyph.height, 2);
    208     pixman.Image.composite32(.over, char, glyph.pix, pix, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
    209 }
    210 
    211 fn formatDatetime() ![]const u8 {
    212     const buf = try state.gpa.alloc(u8, 256);
    213     const now = time.time(null);
    214     const local = time.localtime(&now);
    215     const len = time.strftime(
    216         buf.ptr,
    217         buf.len,
    218         state.config.clockFormat,
    219         local,
    220     );
    221     return state.gpa.realloc(buf, len);
    222 }