diff --git a/res/config.ini b/res/config.ini index cb4f3e1..805792f 100644 --- a/res/config.ini +++ b/res/config.ini @@ -162,7 +162,6 @@ colormix_col3 = 0x20000000 # version -> Ly version string # numlock -> Numlock state # capslock -> Capslock state -# locks -> Both numlock and capslock # labels -> All custom info labels (lbl:) # binds -> All custom keybind hints (cmd:) # lbl:name -> Specific custom info label @@ -170,16 +169,16 @@ colormix_col3 = 0x20000000 # # The order defines the vertical stack (first item is at the edge). -# Top left corner configuration +# Top left corner_top_left = keys -# Top right corner configuration +# Top right corner_top_right = clock tty -# Bottom left corner configuration +# Bottom left corner_bottom_left = version -# Bottom right corner configuration +# Bottom right corner_bottom_right = labels # For custom binds: the horizontal limit in characters for each diff --git a/src/main.zig b/src/main.zig index e6ceda1..503936e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2072,9 +2072,16 @@ const Corner = enum { const PositionedWidgets = struct { binds: []bool, labels: []bool, + keys: bool = false, + clock: bool = false, + tty: bool = false, + battery: bool = false, + version: bool = false, + numlock: bool = false, + capslock: bool = false, }; -fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: usize, is_left: bool, is_top: bool, positioned: *PositionedWidgets) !bool { +fn positionSingleWidget(state: *UiState, item: []const u8, current_x: *usize, current_y: usize, is_left: bool, is_top: bool, positioned: *PositionedWidgets) !bool { const base_x = state.edge_margin.x; if (std.mem.eql(u8, item, "keys")) { @@ -2117,6 +2124,7 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: } } current_x.* = local_x; + positioned.keys = true; return true; } else if (std.mem.eql(u8, item, "clock") or std.mem.eql(u8, item, "time")) { if (state.config.clock == null) return false; @@ -2128,6 +2136,7 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: state.clock_label.positionXY(Position.init(current_x.* - width, current_y)); current_x.* -= width + 1; } + positioned.clock = true; return true; } else if (std.mem.eql(u8, item, "tty")) { if (!state.config.show_tty) return false; @@ -2139,6 +2148,7 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: state.tty_label.positionXY(Position.init(current_x.* - width, current_y)); current_x.* -= width + 1; } + positioned.tty = true; return true; } else if (std.mem.eql(u8, item, "battery")) { if (state.config.battery_id == null) return false; @@ -2150,6 +2160,7 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: state.battery_label.positionXY(Position.init(current_x.* - width, current_y)); current_x.* -= width + 1; } + positioned.battery = true; return true; } else if (std.mem.eql(u8, item, "version")) { if (state.config.hide_version_string) return false; @@ -2161,6 +2172,7 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: state.version_label.positionXY(Position.init(current_x.* - width, current_y)); current_x.* -= width + 1; } + positioned.version = true; return true; } else if (std.mem.eql(u8, item, "numlock")) { if (state.config.hide_keyboard_locks) return false; @@ -2172,6 +2184,7 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: state.numlock_label.positionXY(Position.init(current_x.* - width, current_y)); current_x.* -= width + 1; } + positioned.numlock = true; return true; } else if (std.mem.eql(u8, item, "capslock")) { if (state.config.hide_keyboard_locks) return false; @@ -2183,20 +2196,7 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: state.capslock_label.positionXY(Position.init(current_x.* - width, current_y)); current_x.* -= width + 1; } - return true; - } else if (std.mem.eql(u8, item, "locks")) { - if (state.config.hide_keyboard_locks) return false; - const num_w = TerminalBuffer.strWidth(state.lang.numlock); - const caps_w = TerminalBuffer.strWidth(state.lang.capslock); - if (is_left) { - state.numlock_label.positionXY(Position.init(current_x.*, current_y)); - state.capslock_label.positionXY(Position.init(current_x.* + num_w + 1, current_y)); - current_x.* += num_w + 1 + caps_w + 1; - } else { - state.capslock_label.positionXY(Position.init(current_x.* - caps_w, current_y)); - state.numlock_label.positionXY(Position.init(current_x.* - caps_w - 1 - num_w, current_y)); - current_x.* -= caps_w + 1 + num_w + 1; - } + positioned.capslock = true; return true; } else if (std.mem.eql(u8, item, "labels")) { for (state.custom_info.items, 0..) |*info, i| { @@ -2277,18 +2277,130 @@ fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: return false; } +fn positionItem(state: *UiState, item: []const u8, current_x: *usize, current_y: usize, is_left: bool, is_top: bool, positioned: *PositionedWidgets) !bool { + // Check if item contains a comma for compound widgets + if (std.mem.indexOf(u8, item, ",") != null) { + var it = std.mem.tokenizeAny(u8, item, ","); + var success = false; + + // First, collect all subitems that will be positioned + var subitems: [8][]const u8 = undefined; + var count: usize = 0; + + while (it.next()) |subitem| { + const trimmed = std.mem.trim(u8, subitem, " "); + if (count < subitems.len) { + subitems[count] = trimmed; + count += 1; + } + } + + if (count == 0) return false; + + // Check if we can fit all subitems on this line + var total_width: usize = 0; + for (subitems[0..count]) |subitem| { + // Get width without positioning + const width = try getWidgetWidth(state, subitem); + if (width == 0) continue; + total_width += width + 1; // +1 for spacing + } + if (total_width > 0) total_width -= 1; // Remove last spacing + + // Check if we need to wrap to next line + var line_x = current_x.*; + + // Now position each subitem + for (subitems[0..count]) |subitem| { + if (try positionSingleWidget(state, subitem, &line_x, current_y, is_left, is_top, positioned)) { + success = true; + } + } + + if (success) { + current_x.* = line_x; + } + return success; + } else { + return positionSingleWidget(state, item, current_x, current_y, is_left, is_top, positioned); + } +} + +// Helper function to get widget width without positioning +fn getWidgetWidth(state: *UiState, item: []const u8) !usize { + if (std.mem.eql(u8, item, "keys")) { + if (state.config.hide_key_hints) return 0; + var total: usize = 0; + const labels = [_]?*Label{ + &state.shutdown_label, + &state.restart_label, + if (state.config.sleep_cmd != null) &state.sleep_label else null, + if (state.config.hibernate_cmd != null) &state.hibernate_label else null, + &state.toggle_password_label, + if (state.config.brightness_down_key != null) &state.brightness_down_label else null, + if (state.config.brightness_up_key != null) &state.brightness_up_label else null, + }; + for (labels) |maybe_label| { + const label = maybe_label orelse continue; + total += TerminalBuffer.strWidth(label.text) + 1; + } + return if (total > 0) total - 1 else 0; + } else if (std.mem.eql(u8, item, "numlock")) { + if (state.config.hide_keyboard_locks) return 0; + return TerminalBuffer.strWidth(state.lang.numlock); + } else if (std.mem.eql(u8, item, "capslock")) { + if (state.config.hide_keyboard_locks) return 0; + return TerminalBuffer.strWidth(state.lang.capslock); + } else if (std.mem.eql(u8, item, "clock") or std.mem.eql(u8, item, "time")) { + if (state.config.clock == null) return 0; + return TerminalBuffer.strWidth(state.clock_label.text); + } else if (std.mem.eql(u8, item, "tty")) { + if (!state.config.show_tty) return 0; + return TerminalBuffer.strWidth(state.tty_label.text); + } else if (std.mem.eql(u8, item, "battery")) { + if (state.config.battery_id == null) return 0; + return TerminalBuffer.strWidth(state.battery_label.text); + } else if (std.mem.eql(u8, item, "version")) { + if (state.config.hide_version_string) return 0; + return TerminalBuffer.strWidth(state.version_label.text); + } + return 0; +} + fn positionCorner(state: *UiState, config_str: []const u8, corner: Corner, positioned: *PositionedWidgets) !void { const is_left = corner == .corner_top_left or corner == .corner_bottom_left; - const is_top = corner == .corner_top_right or corner == .corner_bottom_right; + const is_top = corner == .corner_top_left or corner == .corner_top_right; var y_offset: usize = 0; - var it = std.mem.tokenizeAny(u8, config_str, " []\""); + var i: usize = 0; + const len = config_str.len; + + while (i < len) { + // Skip whitespace and brackets + while (i < len and (config_str[i] == ' ' or config_str[i] == '\t' or config_str[i] == '[' or config_str[i] == ']')) { + i += 1; + } + if (i >= len) break; + + var token: []const u8 = undefined; + if (config_str[i] == '"') { + i += 1; + const start = i; + while (i < len and config_str[i] != '"') i += 1; + token = config_str[start..i]; + if (i < len) i += 1; // skip closing quote + } else { + const start = i; + while (i < len and config_str[i] != ' ' and config_str[i] != '\t' and config_str[i] != '[' and config_str[i] != ']') { + i += 1; + } + token = config_str[start..i]; + } - while (it.next()) |item| { var current_x = if (is_left) state.edge_margin.x else state.buffer.width - state.edge_margin.x; const current_y = if (is_top) state.edge_margin.y + y_offset else state.buffer.height - 1 - state.edge_margin.y - y_offset; - if (try positionItem(state, item, corner, ¤t_x, current_y, is_left, is_top, positioned)) { + if (try positionItem(state, token, ¤t_x, current_y, is_left, is_top, positioned)) { y_offset += 1; } } @@ -2297,90 +2409,44 @@ fn positionCorner(state: *UiState, config_str: []const u8, corner: Corner, posit fn positionWidgets(ptr: *anyopaque) !void { var state: *UiState = @ptrCast(@alignCast(ptr)); - // Offsets for custom bind placement. Declared here instead of the - // below if stmt as we need these for `battery_label` positioning. - var x_offset: usize = 0; - // To account for the first row of built-in key hints - var y_offset: usize = 1; - if (!state.config.hide_key_hints) { - state.shutdown_label.positionX(state.edge_margin - .add(TerminalBuffer.START_POSITION)); - var last_label = state.shutdown_label; - state.restart_label.positionX(last_label - .childrenPosition() - .addX(1)); - last_label = state.restart_label; - state.sleep_label.positionX(last_label - .childrenPosition() - .addX(1)); - if (state.config.sleep_cmd != null) { - last_label = state.sleep_label; - } - state.hibernate_label.positionX(last_label - .childrenPosition() - .addX(1)); - if (state.config.hibernate_cmd != null) { - last_label = state.hibernate_label; - } - state.toggle_password_label.positionX(last_label - .childrenPosition() - .addX(1)); - last_label = state.toggle_password_label; - state.brightness_down_label.positionX(last_label - .childrenPosition() - .addX(1)); - if (state.config.brightness_down_key != null) { - last_label = state.brightness_down_label; - } - state.brightness_up_label.positionXY(last_label - .childrenPosition() - .addX(1)); - for (state.custom_binds.items) |*item| { - item.lbl.positionXY(state.edge_margin - .addY(y_offset) - .addX(x_offset)); - x_offset += item.lbl.text.len + 1; - if (x_offset + item.lbl.text.len > state.config.custom_bind_width orelse state.buffer.width) { - x_offset = 0; - y_offset += 1; - } - } + const offscreen = Position.init(9999, 9999); + + // Reset all potential corner widgets to offscreen + state.shutdown_label.positionXY(offscreen); + state.restart_label.positionXY(offscreen); + state.sleep_label.positionXY(offscreen); + state.hibernate_label.positionXY(offscreen); + state.toggle_password_label.positionXY(offscreen); + state.brightness_down_label.positionXY(offscreen); + state.brightness_up_label.positionXY(offscreen); + state.clock_label.positionXY(offscreen); + state.tty_label.positionXY(offscreen); + state.battery_label.positionXY(offscreen); + state.version_label.positionXY(offscreen); + state.numlock_label.positionXY(offscreen); + state.capslock_label.positionXY(offscreen); + + for (state.custom_binds.items) |*bind| { + bind.lbl.positionXY(offscreen); } - for (state.custom_info.items, 0..) |*item, i| { - item.lbl.positionXY(state.edge_margin - .invertX(state.buffer.width) - .removeX(item.lbl.text.len) - .invertY(state.buffer.height) - .removeY(state.custom_info.items.len) - .addY(i)); + for (state.custom_info.items) |*info| { + info.lbl.positionXY(offscreen); } - state.battery_label.positionXY(state.edge_margin - .add(TerminalBuffer.START_POSITION) - .addYFromIf(state.brightness_up_label.childrenPosition(), !state.config.hide_key_hints) - .addYIf(y_offset, !state.config.hide_key_hints) - .removeYFromIf(state.edge_margin, !state.config.hide_key_hints)); + var positioned = PositionedWidgets{ + .binds = try state.allocator.alloc(bool, state.custom_binds.items.len), + .labels = try state.allocator.alloc(bool, state.custom_info.items.len), + }; + defer state.allocator.free(positioned.binds); + defer state.allocator.free(positioned.labels); - const tty_label_width = if (state.config.show_tty) TerminalBuffer.strWidth(state.tty_label.text) else 0; - const tty_label_gap = if (state.config.show_tty and state.config.clock != null) @as(usize, 1) else 0; - state.tty_label.positionXY(state.edge_margin - .add(TerminalBuffer.START_POSITION) - .invertX(state.buffer.width) - .removeXIf(tty_label_width, state.buffer.width > tty_label_width + state.edge_margin.x)); - state.clock_label.positionXY(state.edge_margin - .add(TerminalBuffer.START_POSITION) - .invertX(state.buffer.width) - .removeXIf(TerminalBuffer.strWidth(state.clock_label.text) + tty_label_width + tty_label_gap, state.buffer.width > TerminalBuffer.strWidth(state.clock_label.text) + tty_label_width + tty_label_gap + state.edge_margin.x)); + @memset(positioned.binds, false); + @memset(positioned.labels, false); - state.numlock_label.positionX(state.edge_margin - .add(TerminalBuffer.START_POSITION) - .addYFromIf(state.clock_label.childrenPosition(), state.config.clock != null) - .removeYFromIf(state.edge_margin, state.config.clock != null) - .invertX(state.buffer.width) - .removeXIf(TerminalBuffer.strWidth(state.lang.numlock), state.buffer.width > TerminalBuffer.strWidth(state.lang.numlock) + state.edge_margin.x)); - state.capslock_label.positionX(state.numlock_label - .childrenPosition() - .removeX(TerminalBuffer.strWidth(state.lang.numlock) + TerminalBuffer.strWidth(state.lang.capslock) + 1)); + try positionCorner(state, state.config.corner_top_left, .corner_top_left, &positioned); + try positionCorner(state, state.config.corner_top_right, .corner_top_right, &positioned); + try positionCorner(state, state.config.corner_bottom_left, .corner_bottom_left, &positioned); + try positionCorner(state, state.config.corner_bottom_right, .corner_bottom_right, &positioned); var bb_height = state.box.height; var bb_width = state.box.width; @@ -2447,10 +2513,6 @@ fn positionWidgets(ptr: *anyopaque) !void { state.password.positionY(state.password_label .childrenPosition() .addX(state.labels_max_length - TerminalBuffer.strWidth(state.password_label.text) + 1)); - - state.version_label.positionXY(state.edge_margin - .add(TerminalBuffer.START_POSITION) - .invertY(state.buffer.height - 1)); } fn handleInactivity(ptr: *anyopaque) !void {