diff --git a/build.zig.zon b/build.zig.zon index f26f553..97ed353 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -18,8 +18,8 @@ .hash = "1220220dbc7fe91c1c54438193ca765cebbcb7d58f35cdcaee404a9d2245a42a4362", }, .thespian = .{ - .url = "https://github.com/neurocyte/thespian/archive/448d130c7c772cd09a0de7a197a7866954716fe1.tar.gz", - .hash = "1220c289ad35fb2ee0eb82cc66e000af003259d8bf70a9de6d41ae18a68939d9ac82", + .url = "https://github.com/neurocyte/thespian/archive/db3ad5f45e707a04eaa51aa657995abe43ce967a.tar.gz", + .hash = "1220bbfd147f41fa49d2e5406096f3529c62e9335f4d2a89ae381e679a76ce398f1f", }, .themes = .{ .url = "https://github.com/neurocyte/flow-themes/releases/download/master-8b79cf6d79373c142393ec26a81b19f4701f4372/flow-themes.tar.gz", diff --git a/src/main.zig b/src/main.zig index b9730c1..afc2c3f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -577,7 +577,7 @@ fn config_eql(comptime T: type, a: T, b: T) bool { else => {}, } switch (@typeInfo(T)) { - .Bool, .Int, .Float => return a == b, + .Bool, .Int, .Float, .Enum => return a == b, else => {}, } @compileError("unsupported config type " ++ @typeName(T)); diff --git a/src/tui/status/tabs.zig b/src/tui/status/tabs.zig index dc5c29c..e148113 100644 --- a/src/tui/status/tabs.zig +++ b/src/tui/status/tabs.zig @@ -1,5 +1,6 @@ const std = @import("std"); const tp = @import("thespian"); +const root = @import("root"); const EventHandler = @import("EventHandler"); const Plane = @import("renderer").Plane; @@ -10,8 +11,46 @@ const Widget = @import("../Widget.zig"); const WidgetList = @import("../WidgetList.zig"); const Button = @import("../Button.zig"); -const dirty_indicator = " "; -const padding = " "; +const @"style.config" = struct { + dirty_indicator: []const u8 = " ", + + spacer: []const u8 = "|", + spacer_fg: colors = .active_bg, + spacer_bg: colors = .inactive_bg, + + bar_fg: colors = .inactive_fg, + bar_bg: colors = .inactive_bg, + + active_fg: colors = .active_fg, + active_bg: colors = .active_bg, + active_left: []const u8 = "◢█", + active_left_fg: colors = .active_bg, + active_left_bg: colors = .inactive_bg, + active_right: []const u8 = "█◣", + active_right_fg: colors = .active_bg, + active_right_bg: colors = .inactive_bg, + + inactive_fg: colors = .inactive_fg, + inactive_bg: colors = .inactive_bg, + inactive_left: []const u8 = " ", + inactive_left_fg: colors = .inactive_fg, + inactive_left_bg: colors = .inactive_bg, + inactive_right: []const u8 = " ", + inactive_right_fg: colors = .inactive_fg, + inactive_right_bg: colors = .inactive_bg, + + selected_fg: colors = .active_fg, + selected_bg: colors = .active_bg, + selected_left: []const u8 = "◢█", + selected_left_fg: colors = .active_bg, + selected_left_bg: colors = .inactive_bg, + selected_right: []const u8 = "█◣", + selected_right_fg: colors = .active_bg, + selected_right_bg: colors = .inactive_bg, + + include_files: []const u8 = "", +}; +const Style = @"style.config"; pub fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler) @import("widget.zig").CreateError!Widget { const self = try allocator.create(TabBar); @@ -28,6 +67,9 @@ const TabBar = struct { tabs: []TabBarTab = &[_]TabBarTab{}, active_buffer: ?*Buffer = null, + tab_style: Style, + tab_style_bufs: [][]const u8, + const Self = @This(); const TabBarTab = struct { @@ -38,16 +80,21 @@ const TabBar = struct { fn init(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler) !Self { var w = try WidgetList.createH(allocator, parent, "tabs", .dynamic); w.ctx = w; + const tab_style, const tab_style_bufs = root.read_config(Style, allocator); + root.write_config(tab_style, allocator) catch {}; return .{ .allocator = allocator, .plane = w.plane, .widget_list = w, .widget_list_widget = w.widget(), .event_handler = event_handler, + .tab_style = tab_style, + .tab_style_bufs = tab_style_bufs, }; } pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + root.free_config(self.allocator, self.tab_style_bufs); self.allocator.free(self.tabs); self.widget_list_widget.deinit(allocator); allocator.destroy(self); @@ -67,7 +114,10 @@ const TabBar = struct { self.plane.set_base_style(theme.editor); self.plane.erase(); self.plane.home(); - self.plane.set_style(theme.tab_inactive); + self.plane.set_style(.{ + .fg = self.tab_style.bar_fg.from_theme(theme), + .bg = self.tab_style.bar_bg.from_theme(theme), + }); self.plane.fill(" "); self.plane.home(); return self.widget_list_widget.render(theme); @@ -146,7 +196,7 @@ const TabBar = struct { if (!buffer.hidden) (try result.addOne(self.allocator)).* = .{ .buffer = buffer, - .widget = try Tab.create(self, buffer, self.event_handler), + .widget = try Tab.create(self, buffer, &self.tab_style, self.event_handler), }; } @@ -154,7 +204,14 @@ const TabBar = struct { } fn make_spacer(self: @This()) !Widget { - return spacer.create(self.allocator, self.widget_list.plane, null); + return spacer.create( + self.allocator, + self.widget_list.plane, + self.tab_style.spacer, + self.tab_style.spacer_fg, + self.tab_style.spacer_bg, + null, + ); } fn select_next_tab(self: *Self) void { @@ -192,14 +249,18 @@ const TabBar = struct { const Tab = struct { tabbar: *TabBar, buffer: *Buffer, + tab_style: *const Style, + + const Mode = enum { active, inactive, selected }; fn create( tabbar: *TabBar, buffer: *Buffer, + tab_style: *const Style, event_handler: ?EventHandler, ) !Widget { return Button.create_widget(Tab, tabbar.allocator, tabbar.widget_list.plane, .{ - .ctx = .{ .tabbar = tabbar, .buffer = buffer }, + .ctx = .{ .tabbar = tabbar, .buffer = buffer, .tab_style = tab_style }, .label = name_from_buffer(buffer), .on_click = Tab.on_click, .on_click2 = Tab.on_click2, @@ -219,57 +280,140 @@ const Tab = struct { fn render(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) bool { const active = self.tabbar.active_buffer == self.buffer; - return if (active) - self.render_active(btn, theme) - else - self.render_inactive(btn, theme); - } - - fn render_active(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) bool { - btn.plane.set_base_style(theme.editor); - btn.plane.erase(); - btn.plane.home(); - btn.plane.set_style(theme.tab_inactive); - btn.plane.fill(" "); - btn.plane.home(); - btn.plane.set_style(theme.tab_active); - btn.plane.fill(" "); - btn.plane.home(); - return self.render_content(btn); - } - - fn render_inactive(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) bool { - btn.plane.set_base_style(theme.editor); - btn.plane.erase(); - btn.plane.home(); - btn.plane.set_style(theme.tab_inactive); - btn.plane.fill(" "); - btn.plane.home(); - if (btn.hover) { - btn.plane.set_style(theme.tab_selected); - btn.plane.fill(" "); - btn.plane.home(); + const mode: Mode = if (btn.hover) .selected else if (active) .active else .inactive; + switch (mode) { + .selected => self.render_selected(btn, theme, active), + .active => self.render_active(btn, theme), + .inactive => self.render_inactive(btn, theme), } - return self.render_content(btn); - } - - fn render_content(self: *@This(), btn: *Button.State(@This())) bool { - _ = btn.plane.putstr(" ") catch {}; - if (self.buffer.is_dirty()) - _ = btn.plane.putstr(dirty_indicator) catch {}; - _ = btn.plane.putstr(btn.opts.label) catch {}; - _ = btn.plane.putstr(" ") catch {}; return false; } + fn render_selected(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme, active: bool) void { + btn.plane.set_base_style(theme.editor); + btn.plane.erase(); + btn.plane.home(); + btn.plane.set_style(.{ + .fg = self.tab_style.inactive_fg.from_theme(theme), + .bg = self.tab_style.inactive_bg.from_theme(theme), + }); + btn.plane.fill(" "); + btn.plane.home(); + if (active) { + btn.plane.set_style(.{ + .fg = self.tab_style.selected_fg.from_theme(theme), + .bg = self.tab_style.selected_bg.from_theme(theme), + }); + btn.plane.fill(" "); + btn.plane.home(); + } + + btn.plane.set_style(.{ + .fg = self.tab_style.selected_left_fg.from_theme(theme), + .bg = self.tab_style.selected_left_bg.from_theme(theme), + }); + _ = btn.plane.putstr(self.tab_style.selected_left) catch {}; + + btn.plane.set_style(.{ + .fg = self.tab_style.selected_fg.from_theme(theme), + .bg = self.tab_style.selected_bg.from_theme(theme), + }); + self.render_content(btn); + + btn.plane.set_style(.{ + .fg = self.tab_style.selected_right_fg.from_theme(theme), + .bg = self.tab_style.selected_right_bg.from_theme(theme), + }); + _ = btn.plane.putstr(self.tab_style.selected_right) catch {}; + } + + fn render_active(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) void { + btn.plane.set_base_style(theme.editor); + btn.plane.erase(); + btn.plane.home(); + btn.plane.set_style(.{ + .fg = self.tab_style.inactive_fg.from_theme(theme), + .bg = self.tab_style.inactive_bg.from_theme(theme), + }); + btn.plane.fill(" "); + btn.plane.home(); + btn.plane.set_style(.{ + .fg = self.tab_style.active_fg.from_theme(theme), + .bg = self.tab_style.active_bg.from_theme(theme), + }); + btn.plane.fill(" "); + btn.plane.home(); + + btn.plane.set_style(.{ + .fg = self.tab_style.active_left_fg.from_theme(theme), + .bg = self.tab_style.active_left_bg.from_theme(theme), + }); + _ = btn.plane.putstr(self.tab_style.active_left) catch {}; + + btn.plane.set_style(.{ + .fg = self.tab_style.active_fg.from_theme(theme), + .bg = self.tab_style.active_bg.from_theme(theme), + }); + self.render_content(btn); + + btn.plane.set_style(.{ + .fg = self.tab_style.active_right_fg.from_theme(theme), + .bg = self.tab_style.active_right_bg.from_theme(theme), + }); + _ = btn.plane.putstr(self.tab_style.active_right) catch {}; + } + + fn render_inactive(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) void { + btn.plane.set_base_style(theme.editor); + btn.plane.erase(); + btn.plane.home(); + btn.plane.set_style(.{ + .fg = self.tab_style.inactive_fg.from_theme(theme), + .bg = self.tab_style.inactive_bg.from_theme(theme), + }); + btn.plane.fill(" "); + btn.plane.home(); + + btn.plane.set_style(.{ + .fg = self.tab_style.inactive_left_fg.from_theme(theme), + .bg = self.tab_style.inactive_left_bg.from_theme(theme), + }); + _ = btn.plane.putstr(self.tab_style.inactive_left) catch {}; + + btn.plane.set_style(.{ + .fg = self.tab_style.inactive_fg.from_theme(theme), + .bg = self.tab_style.inactive_bg.from_theme(theme), + }); + self.render_content(btn); + + btn.plane.set_style(.{ + .fg = self.tab_style.inactive_right_fg.from_theme(theme), + .bg = self.tab_style.inactive_right_bg.from_theme(theme), + }); + _ = btn.plane.putstr(self.tab_style.inactive_right) catch {}; + } + + fn render_content(self: *@This(), btn: *Button.State(@This())) void { + if (self.buffer.is_dirty()) + _ = btn.plane.putstr(self.tabbar.tab_style.dirty_indicator) catch {}; + _ = btn.plane.putstr(btn.opts.label) catch {}; + } + fn layout(self: *@This(), btn: *Button.State(@This())) Widget.Layout { + const active = self.tabbar.active_buffer == self.buffer; const len = btn.plane.egc_chunk_width(btn.opts.label, 0, 1); - const len_dirty_indicator = btn.plane.egc_chunk_width(dirty_indicator, 0, 1); - const len_padding = btn.plane.egc_chunk_width(padding, 0, 1); - return if (self.buffer.is_dirty()) - .{ .static = len + (2 * len_padding) + len_dirty_indicator } + const len_padding = padding_len(btn.plane, self.tabbar.tab_style, active, self.buffer.is_dirty()); + return .{ .static = len + len_padding }; + } + + fn padding_len(plane: Plane, tab_style: Style, active: bool, dirty: bool) usize { + const len_dirty_indicator = if (dirty) plane.egc_chunk_width(tab_style.dirty_indicator, 0, 1) else 0; + return len_dirty_indicator + if (active) + plane.egc_chunk_width(tab_style.active_left, 0, 1) + + plane.egc_chunk_width(tab_style.active_right, 0, 1) else - .{ .static = len + (2 * len_padding) }; + plane.egc_chunk_width(tab_style.inactive_left, 0, 1) + + plane.egc_chunk_width(tab_style.inactive_right, 0, 1); } fn name_from_buffer(buffer: *Buffer) []const u8 { @@ -284,15 +428,28 @@ const spacer = struct { plane: Plane, layout: Widget.Layout, on_event: ?EventHandler, + content: []const u8, + fg: colors, + bg: colors, const Self = @This(); - fn create(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler) @import("widget.zig").CreateError!Widget { + fn create( + allocator: std.mem.Allocator, + parent: Plane, + content: []const u8, + fg: colors, + bg: colors, + event_handler: ?EventHandler, + ) @import("widget.zig").CreateError!Widget { const self: *Self = try allocator.create(Self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), - .layout = .{ .static = 1 }, + .layout = .{ .static = self.plane.egc_chunk_width(content, 0, 1) }, .on_event = event_handler, + .content = content, + .fg = fg, + .bg = bg, }; return Widget.to(self); } @@ -310,9 +467,13 @@ const spacer = struct { self.plane.set_base_style(theme.editor); self.plane.erase(); self.plane.home(); - self.plane.set_style(theme.tab_inactive); + self.plane.set_style(.{ + .fg = self.fg.from_theme(theme), + .bg = self.bg.from_theme(theme), + }); self.plane.fill(" "); self.plane.home(); + _ = self.plane.putstr(self.content) catch {}; return false; } @@ -325,3 +486,27 @@ const spacer = struct { return false; } }; + +const colors = enum { + default_bg, + default_fg, + active_bg, + active_fg, + inactive_bg, + inactive_fg, + selected_bg, + selected_fg, + + fn from_theme(color: colors, theme: *const Widget.Theme) ?Widget.Theme.Color { + return switch (color) { + .default_bg => theme.editor.bg, + .default_fg => theme.editor.fg, + .active_bg => theme.tab_active.bg, + .active_fg => theme.tab_active.fg, + .inactive_bg => theme.tab_inactive.bg, + .inactive_fg => theme.tab_inactive.fg, + .selected_bg => theme.tab_selected.bg, + .selected_fg => theme.tab_selected.fg, + }; + } +};