diff --git a/res/init.sh b/res/init.sh new file mode 100644 index 0000000..1f442f2 --- /dev/null +++ b/res/init.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# Styling options +local BOLD = 0x01000000 +local UNDERLINE = 0x02000000 +local REVERSE = 0x04000000 +local ITALIC = 0x08000000 +local BLINK = 0x10000000 +local HI_BLACK = 0x20000000 +local BRIGHT = 0x40000000 +local DIM = 0x80000000 + +# Common colors +local DEFAULT = 0x00000000 +local RED = 0x00FF0000 +local GREEN = 0x0000FF00 +local YELLOW = 0x00FFFF00 +local BLUE = 0x000000FF +local MAGENTA = 0x00FF00FF +local CYAN = 0x0000FFFF +local WHITE = 0x00FFFFFF + +source lang/en.sh # From lang + +# It'd be a good idea to condense multiple options into 1 command (e.g. +# animation settings) +ly set-config allow_empty_password true +ly set-config animation none +ly set-config animation_timeout_sec 0 +ly set-config asterisk "*" +ly set-config auth_fails 10 +ly set-config bg $DEFAULT +ly set-config bigclock_12hr false +ly set-config bigclock_seconds false +ly set-config blank_box true +ly set-config border_fg $WHITE +ly set-config box_title null +ly set-config clear_password false +ly set-config clock null +ly set-config cmatrix_fg $GREEN +ly set-config cmatrix_head_col $(($WHITE | $BOLD)) +ly set-config cmatrix_min_codepoint 0x21 +ly set-config cmatrix_max_codepoint = 0x7B +ly set-config colormix_col1 $RED +ly set-config colormix_col2 $BLUE +ly set-config colormix_col3 $HI_BLACK +ly set-config default_input login +ly set-config doom_fire_height 6 +ly set-config doom_fire_spread 2 +ly set-config doom_top_color 0x009F2707 +ly set-config doom_middle_color 0x00C78F17 +ly set-config doom_bottom_color $WHITE +ly set-config error_bg $DEFAULT +ly set-config error_fg $(($RED | $BOLD)) +ly set-config fg $WHITE +ly set-config full_color true +ly set-config gameoflife_entropy_interval 10 +ly set-config gameoflife_fg $GREEN +ly set-config gameoflife_frame_delay 6 +ly set-config gameoflife_initial_density 0.4 +ly set-config hide_borders false +ly set-config initial_info_text null +ly set-config input_len 34 +ly set-config login_cmd null +ly set-config login_defs_path "/etc/login.defs" +ly set-config logout_cmd null +ly set-config ly_log "/var/log/ly.log" +ly set-config margin_box_h 2 +ly set-config margin_box_v 1 +ly set-config min_refresh_delta 5 +ly set-config numlock false +ly set-config path "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +ly set-config save true +ly set-config service_name ly +ly set-config session_log "ly-session.log" +ly set-config setup_cmd "$CONFIG_DIRECTORY/ly/setup.sh" +ly set-config text_in_center false +ly set-config vi_default_mode normal +ly set-config vi_mode false +ly set-config x_cmd "$PREFIX_DIRECTORY/bin/X" +ly set-config xauth_cmd "$PREFIX_DIRECTORY/bin/xauth" + +# Replaces respective options +# X11 requires special support from the display manager, which is why a separate +# command is required +ly add-session "System shell" shell "/bin/sh" +ly add-x11-session xinitrc x11 "~/.xinitrc" + +# ly add-session-dir custom "$CONFIG_DIRECTORY/ly/custom-sessions" +ly add-session-dir wayland "$PREFIX_DIRECTORY/share/wayland-sessions" +ly add-x11-session-dir x11 "$PREFIX_DIRECTORY/share/xsessions" + +ly add-hud 0 0 "Ly version %VERSION" null null top left +ly add-hud 1 0 "F1 shutdown" F1 "/sbin/shutdown -a now" top left +ly add-hud 2 0 "F2 reboot" F2 "/sbin/shutdown -r now" top left +#ly add-hud 0 0 "F3 sleep" F3 null top left +ly add-hud 3 0 "F5 decrease brightness" F5 "$PREFIX_DIRECTORY/bin/brightnessctl -q s 10%-" top left +ly add-hud 4 0 "F6 increase brightness" F6 "$PREFIX_DIRECTORY/bin/brightnessctl -q s +10%" top left +ly add-hud 5 0 "%CLOCK" null null top right diff --git a/src/config/Save.zig b/src/config/OldSave.zig similarity index 100% rename from src/config/Save.zig rename to src/config/OldSave.zig diff --git a/src/config/SavedUsers.zig b/src/config/SavedUsers.zig new file mode 100644 index 0000000..dd08eb4 --- /dev/null +++ b/src/config/SavedUsers.zig @@ -0,0 +1,22 @@ +const std = @import("std"); + +const SavedUsers = @This(); + +const User = struct { + username: []const u8, + session_index: usize, +}; + +user_list: std.ArrayList(User), +last_username_index: ?usize, + +pub fn init() SavedUsers { + return .{ + .user_list = .empty, + .last_username_index = null, + }; +} + +pub fn deinit(self: *SavedUsers, allocator: std.mem.Allocator) void { + self.user_list.deinit(allocator); +} diff --git a/src/config/migrator.zig b/src/config/migrator.zig index bdbfe67..bfba870 100644 --- a/src/config/migrator.zig +++ b/src/config/migrator.zig @@ -5,7 +5,8 @@ const std = @import("std"); const ini = @import("zigini"); const Config = @import("Config.zig"); -const Save = @import("Save.zig"); +const OldSave = @import("OldSave.zig"); +const SavedUsers = @import("SavedUsers.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const Color = TerminalBuffer.Color; @@ -186,8 +187,8 @@ pub fn lateConfigFieldHandler(config: *Config) void { } } -pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save { - var save = Save{}; +pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave { + var save = OldSave{}; if (maybe_save_file) |path| { defer temporary_allocator.free(path); @@ -216,3 +217,29 @@ pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save { return save; } + +pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, save_ini: *ini.Ini(OldSave), path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !bool { + var old_save_file_exists = true; + + var user_buf: [32]u8 = undefined; + const save = save_ini.readFileToStruct(path, .{ + .fieldHandler = null, + .comment_characters = "#", + }) catch no_save_file: { + old_save_file_exists = false; + break :no_save_file tryMigrateFirstSaveFile(&user_buf); + }; + + if (!old_save_file_exists) return false; + + // Add all other users to the list + for (usernames, 0..) |username, i| { + if (save.user) |user| { + if (std.mem.eql(u8, user, username)) saved_users.last_username_index = i; + } + + try saved_users.user_list.append(allocator, .{ .username = username, .session_index = save.session_index orelse 0 }); + } + + return true; +} diff --git a/src/main.zig b/src/main.zig index fff3857..102e53d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -21,7 +21,8 @@ const InfoLine = @import("tui/components/InfoLine.zig"); const UserList = @import("tui/components/UserList.zig"); const Config = @import("config/Config.zig"); const Lang = @import("config/Lang.zig"); -const Save = @import("config/Save.zig"); +const OldSave = @import("config/OldSave.zig"); +const SavedUsers = @import("config/SavedUsers.zig"); const migrator = @import("config/migrator.zig"); const SharedError = @import("SharedError.zig"); const UidRange = @import("UidRange.zig"); @@ -118,11 +119,14 @@ pub fn main() !void { var config: Config = undefined; var lang: Lang = undefined; - var save: Save = undefined; + var old_save_file_exists = false; var maybe_config_load_error: ?anyerror = null; var can_get_lock_state = true; var can_draw_clock = true; + var saved_users = SavedUsers.init(); + defer saved_users.deinit(allocator); + if (res.args.help != 0) { try clap.help(stderr, clap.Help, ¶ms, .{}); @@ -143,13 +147,15 @@ pub fn main() !void { var lang_ini = Ini(Lang).init(allocator); defer lang_ini.deinit(); - var save_ini = Ini(Save).init(allocator); - defer save_ini.deinit(); + var old_save_ini = ini.Ini(OldSave).init(allocator); + defer old_save_ini.deinit(); - var save_path: []const u8 = build_options.config_directory ++ "/ly/save.ini"; + var save_path: []const u8 = build_options.config_directory ++ "/ly/save.txt"; + var old_save_path: []const u8 = build_options.config_directory ++ "/ly/save.ini"; var save_path_alloc = false; defer { if (save_path_alloc) allocator.free(save_path); + if (save_path_alloc) allocator.free(old_save_path); } const comment_characters = "#"; @@ -178,18 +184,9 @@ pub fn main() !void { }) catch Lang{}; if (config.save) { - save_path = try std.fmt.allocPrint(allocator, "{s}{s}save.ini", .{ s, trailing_slash }); + save_path = try std.fmt.allocPrint(allocator, "{s}{s}save.txt", .{ s, trailing_slash }); + old_save_path = try std.fmt.allocPrint(allocator, "{s}{s}save.ini", .{ s, trailing_slash }); save_path_alloc = true; - - var user_buf: [32]u8 = undefined; - save = save_ini.readFileToStruct(save_path, .{ - .fieldHandler = null, - .comment_characters = comment_characters, - }) catch migrator.tryMigrateSaveFile(&user_buf); - } - - if (maybe_config_load_error == null) { - migrator.lateConfigFieldHandler(&config); } } else { const config_path = build_options.config_directory ++ "/ly/config.ini"; @@ -210,17 +207,47 @@ pub fn main() !void { .fieldHandler = null, .comment_characters = comment_characters, }) catch Lang{}; + } - if (config.save) { - var user_buf: [32]u8 = undefined; - save = save_ini.readFileToStruct(save_path, .{ - .fieldHandler = null, - .comment_characters = comment_characters, - }) catch migrator.tryMigrateSaveFile(&user_buf); - } + if (maybe_config_load_error == null) { + migrator.lateConfigFieldHandler(&config); + } - if (maybe_config_load_error == null) { - migrator.lateConfigFieldHandler(&config); + var usernames = try getAllUsernames(allocator, config.login_defs_path); + defer { + for (usernames.items) |username| allocator.free(username); + usernames.deinit(allocator); + } + + if (config.save) read_save_file: { + old_save_file_exists = migrator.tryMigrateIniSaveFile(allocator, &old_save_ini, old_save_path, &saved_users, usernames.items) catch break :read_save_file; + + // Don't read the new save file if the old one still exists + if (old_save_file_exists) break :read_save_file; + + var save_file = std.fs.cwd().openFile(save_path, .{}) catch break :read_save_file; + defer save_file.close(); + + var file_buffer: [256]u8 = undefined; + var file_reader = save_file.reader(&file_buffer); + var reader = &file_reader.interface; + + const last_username_index_str = reader.takeDelimiterInclusive('\n') catch break :read_save_file; + saved_users.last_username_index = std.fmt.parseInt(usize, last_username_index_str[0..(last_username_index_str.len - 1)], 10) catch break :read_save_file; + + while (reader.seek < reader.buffer.len) { + const line = reader.takeDelimiterInclusive('\n') catch break; + + var user = std.mem.splitScalar(u8, line[0..(line.len - 1)], ':'); + const username = user.next() orelse continue; + const session_index_str = user.next() orelse continue; + + const session_index = std.fmt.parseInt(usize, session_index_str, 10) catch continue; + + try saved_users.user_list.append(allocator, .{ + .username = username, + .session_index = session_index, + }); } } @@ -345,7 +372,9 @@ pub fn main() !void { try log_writer.print("failed to set numlock: {s}\n", .{@errorName(err)}); }; - var session = Session.init(allocator, &buffer); + var login: UserList = undefined; + + var session = Session.init(allocator, &buffer, &login); defer session.deinit(); addOtherEnvironment(&session, lang, .shell, null) catch |err| { @@ -396,12 +425,6 @@ pub fn main() !void { try crawl(&session, lang, dir, .custom); } - var usernames = try getAllUsernames(allocator, config.login_defs_path); - defer { - for (usernames.items) |username| allocator.free(username); - usernames.deinit(allocator); - } - if (usernames.items.len == 0) { // If we have no usernames, simply add an error to the info line. // This effectively means you can't login, since there would be no local @@ -411,7 +434,7 @@ pub fn main() !void { try log_writer.writeAll("no users found\n"); } - var login = try UserList.init(allocator, &buffer, usernames); + login = try UserList.init(allocator, &buffer, usernames, &saved_users, &session); defer login.deinit(); var password = Text.init(allocator, &buffer, true, config.asterisk); @@ -422,23 +445,21 @@ pub fn main() !void { // Load last saved username and desktop selection, if any if (config.save) { - if (save.user) |user| { + if (saved_users.last_username_index) |index| { + const user = saved_users.user_list.items[index]; + // Find user with saved name, and switch over to it // If it doesn't exist (anymore), we don't change the value - // Note that we could instead save the username index, but migrating - // from the raw username to an index is non-trivial and I'm lazy :P for (usernames.items, 0..) |username, i| { - if (std.mem.eql(u8, username, user)) { + if (std.mem.eql(u8, username, user.username)) { login.label.current = i; break; } } active_input = .password; - } - if (save.session_index) |session_index| { - if (session_index < session.label.list.items.len) session.label.current = session_index; + if (user.session_index < session.label.list.items.len) session.label.current = user.session_index; } } @@ -853,22 +874,33 @@ pub fn main() !void { _ = termbox.tb_present(); if (config.save) save_last_settings: { - var file = std.fs.cwd().createFile(save_path, .{}) catch break :save_last_settings; + // It isn't worth cluttering the code with precise error + // handling, so let's just report a generic error message, + // that should be good enough for debugging anyway. + errdefer log_writer.writeAll("failed to save current user data\n") catch {}; + + var file = std.fs.cwd().createFile(save_path, .{}) catch |err| { + log_writer.print("failed to create save file: {s}\n", .{@errorName(err)}) catch break :save_last_settings; + break :save_last_settings; + }; defer file.close(); - var file_buffer: [64]u8 = undefined; + var file_buffer: [256]u8 = undefined; var file_writer = file.writer(&file_buffer); var writer = &file_writer.interface; - const save_data = Save{ - .user = login.getCurrentUser(), - .session_index = session.label.current, - }; - ini.writeFromStruct(save_data, writer, null, .{}) catch break :save_last_settings; + try writer.print("{d}\n", .{login.label.current}); + for (saved_users.user_list.items) |user| { + try writer.print("{s}:{d}\n", .{ user.username, user.session_index }); + } try writer.flush(); // Delete previous save file if it exists - if (migrator.maybe_save_file) |path| std.fs.cwd().deleteFile(path) catch {}; + if (migrator.maybe_save_file) |path| { + std.fs.cwd().deleteFile(path) catch {}; + } else if (old_save_file_exists) { + std.fs.cwd().deleteFile(old_save_path) catch {}; + } } var shared_err = try SharedError.init(); @@ -877,7 +909,7 @@ pub fn main() !void { { session_pid = try std.posix.fork(); if (session_pid == 0) { - const current_environment = session.label.list.items[session.label.current]; + const current_environment = session.label.list.items[session.label.current].environment; const auth_options = auth.AuthOptions{ .tty = active_tty, .service_name = config.service_name, @@ -898,7 +930,7 @@ pub fn main() !void { }; std.posix.sigaction(std.posix.SIG.CHLD, &tty_control_transfer_act, null); - auth.authenticate(allocator, log_writer, auth_options, current_environment, login.getCurrentUser(), password.text.items) catch |err| { + auth.authenticate(allocator, log_writer, auth_options, current_environment, login.getCurrentUsername(), password.text.items) catch |err| { shared_err.writeError(err); std.process.exit(1); }; @@ -1026,7 +1058,7 @@ fn addOtherEnvironment(session: *Session, lang: Lang, display_server: DisplaySer .name = name, .xdg_session_desktop = null, .xdg_desktop_names = null, - .cmd = exec orelse "", + .cmd = exec, .specifier = lang.other, .display_server = display_server, .is_terminal = display_server == .shell, diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig index 7d588fb..f31fc11 100644 --- a/src/tui/components/InfoLine.zig +++ b/src/tui/components/InfoLine.zig @@ -4,7 +4,7 @@ const generic = @import("generic.zig"); const Allocator = std.mem.Allocator; -const MessageLabel = generic.CyclableLabel(Message); +const MessageLabel = generic.CyclableLabel(Message, Message); const InfoLine = @This(); @@ -19,7 +19,7 @@ label: MessageLabel, pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine { return .{ - .label = MessageLabel.init(allocator, buffer, drawItem), + .label = MessageLabel.init(allocator, buffer, drawItem, null, null), }; } diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig index 527b978..53a6c90 100644 --- a/src/tui/components/Session.zig +++ b/src/tui/components/Session.zig @@ -3,41 +3,53 @@ const TerminalBuffer = @import("../TerminalBuffer.zig"); const enums = @import("../../enums.zig"); const Environment = @import("../../Environment.zig"); const generic = @import("generic.zig"); +const UserList = @import("UserList.zig"); const Allocator = std.mem.Allocator; const DisplayServer = enums.DisplayServer; -const EnvironmentLabel = generic.CyclableLabel(Environment); + +const Env = struct { + environment: Environment, + index: usize, +}; +const EnvironmentLabel = generic.CyclableLabel(Env, *UserList); const Session = @This(); label: EnvironmentLabel, -pub fn init(allocator: Allocator, buffer: *TerminalBuffer) Session { +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, user_list: *UserList) Session { return .{ - .label = EnvironmentLabel.init(allocator, buffer, drawItem), + .label = EnvironmentLabel.init(allocator, buffer, drawItem, sessionChanged, user_list), }; } pub fn deinit(self: *Session) void { - for (self.label.list.items) |*environment| { - if (environment.entry_ini) |*entry_ini| entry_ini.deinit(); + for (self.label.list.items) |*env| { + if (env.environment.entry_ini) |*entry_ini| entry_ini.deinit(); } self.label.deinit(); } pub fn addEnvironment(self: *Session, environment: Environment) !void { - try self.label.addItem(environment); + try self.label.addItem(.{ .environment = environment, .index = self.label.list.items.len }); } -fn drawItem(label: *EnvironmentLabel, environment: Environment, x: usize, y: usize) bool { - const length = @min(environment.name.len, label.visible_length - 3); +fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void { + if (maybe_user_list) |user_list| { + user_list.label.list.items[user_list.label.current].session_index.* = env.index; + } +} + +fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize) bool { + const length = @min(env.environment.name.len, label.visible_length - 3); if (length == 0) return false; - const nx = if (label.text_in_center) (label.x + (label.visible_length - environment.name.len) / 2) else (label.x + 2); - label.first_char_x = nx + environment.name.len; + const nx = if (label.text_in_center) (label.x + (label.visible_length - env.environment.name.len) / 2) else (label.x + 2); + label.first_char_x = nx + env.environment.name.len; - label.buffer.drawLabel(environment.specifier, x, y); - label.buffer.drawLabel(environment.name, nx, label.y); + label.buffer.drawLabel(env.environment.specifier, x, y); + label.buffer.drawLabel(env.environment.name, nx, label.y); return true; } diff --git a/src/tui/components/UserList.zig b/src/tui/components/UserList.zig index 41eff39..6939298 100644 --- a/src/tui/components/UserList.zig +++ b/src/tui/components/UserList.zig @@ -1,45 +1,83 @@ const std = @import("std"); const TerminalBuffer = @import("../TerminalBuffer.zig"); const generic = @import("generic.zig"); +const Session = @import("Session.zig"); +const SavedUsers = @import("../../config/SavedUsers.zig"); const StringList = std.ArrayListUnmanaged([]const u8); const Allocator = std.mem.Allocator; -const UsernameText = generic.CyclableLabel([]const u8); +pub const User = struct { + name: []const u8, + session_index: *usize, + allocated_index: bool, +}; +const UserLabel = generic.CyclableLabel(User, *Session); const UserList = @This(); -label: UsernameText, +label: UserLabel, -pub fn init(allocator: Allocator, buffer: *TerminalBuffer, usernames: StringList) !UserList { +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, usernames: StringList, saved_users: *SavedUsers, session: *Session) !UserList { var userList = UserList{ - .label = UsernameText.init(allocator, buffer, drawItem), + .label = UserLabel.init(allocator, buffer, drawItem, usernameChanged, session), }; for (usernames.items) |username| { if (username.len == 0) continue; - try userList.label.addItem(username); + var maybe_session_index: ?*usize = null; + for (saved_users.user_list.items) |*saved_user| { + if (std.mem.eql(u8, username, saved_user.username)) { + maybe_session_index = &saved_user.session_index; + break; + } + } + + var allocated_index = false; + if (maybe_session_index == null) { + maybe_session_index = try allocator.create(usize); + maybe_session_index.?.* = 0; + allocated_index = true; + } + + try userList.label.addItem(.{ + .name = username, + .session_index = maybe_session_index.?, + .allocated_index = allocated_index, + }); } return userList; } pub fn deinit(self: *UserList) void { + for (self.label.list.items) |user| { + if (user.allocated_index) { + self.label.allocator.destroy(user.session_index); + } + } + self.label.deinit(); } -pub fn getCurrentUser(self: UserList) []const u8 { - return self.label.list.items[self.label.current]; +pub fn getCurrentUsername(self: UserList) []const u8 { + return self.label.list.items[self.label.current].name; } -fn drawItem(label: *UsernameText, username: []const u8, _: usize, _: usize) bool { - const length = @min(username.len, label.visible_length - 3); +fn usernameChanged(user: User, maybe_session: ?*Session) void { + if (maybe_session) |session| { + session.label.current = user.session_index.*; + } +} + +fn drawItem(label: *UserLabel, user: User, _: usize, _: usize) bool { + const length = @min(user.name.len, label.visible_length - 3); if (length == 0) return false; - const x = if (label.text_in_center) (label.x + (label.visible_length - username.len) / 2) else (label.x + 2); - label.first_char_x = x + username.len; + const x = if (label.text_in_center) (label.x + (label.visible_length - user.name.len) / 2) else (label.x + 2); + label.first_char_x = x + user.name.len; - label.buffer.drawLabel(username, x, label.y); + label.buffer.drawLabel(user.name, x, label.y); return true; } diff --git a/src/tui/components/generic.zig b/src/tui/components/generic.zig index 322bc8f..2f3c25a 100644 --- a/src/tui/components/generic.zig +++ b/src/tui/components/generic.zig @@ -2,11 +2,12 @@ const std = @import("std"); const interop = @import("../../interop.zig"); const TerminalBuffer = @import("../TerminalBuffer.zig"); -pub fn CyclableLabel(comptime ItemType: type) type { +pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type { return struct { const Allocator = std.mem.Allocator; const ItemList = std.ArrayListUnmanaged(ItemType); const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool; + const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void; const termbox = interop.termbox; @@ -22,8 +23,10 @@ pub fn CyclableLabel(comptime ItemType: type) type { first_char_x: usize, text_in_center: bool, draw_item_fn: DrawItemFn, + change_item_fn: ?ChangeItemFn, + change_item_arg: ?ChangeItemType, - pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn) Self { + pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, change_item_arg: ?ChangeItemType) Self { return .{ .allocator = allocator, .buffer = buffer, @@ -35,6 +38,8 @@ pub fn CyclableLabel(comptime ItemType: type) type { .first_char_x = 0, .text_in_center = false, .draw_item_fn = draw_item_fn, + .change_item_fn = change_item_fn, + .change_item_arg = change_item_arg, }; } @@ -94,21 +99,19 @@ pub fn CyclableLabel(comptime ItemType: type) type { } fn goLeft(self: *Self) void { - if (self.current == 0) { - self.current = self.list.items.len - 1; - return; - } + self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1; - self.current -= 1; + if (self.change_item_fn) |change_item_fn| { + @call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg }); + } } fn goRight(self: *Self) void { - if (self.current == self.list.items.len - 1) { - self.current = 0; - return; - } + self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1; - self.current += 1; + if (self.change_item_fn) |change_item_fn| { + @call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg }); + } } }; }