diff --git a/src/button.zig b/src/button.zig index 539ede9..b22cfaa 100644 --- a/src/button.zig +++ b/src/button.zig @@ -48,7 +48,7 @@ pub const Button = struct { _ = term_io; var self: *Button = @fieldParentPtr("pane", pane_ptr); - if (key.type == .ASCII and key.value == '\r') { + if (key.key == .Return) { self.callback(); } diff --git a/src/focus.zig b/src/focus.zig new file mode 100644 index 0000000..e8ccdd0 --- /dev/null +++ b/src/focus.zig @@ -0,0 +1,115 @@ +const std = @import("std"); +const pane = @import("pane.zig"); +const term = @import("term.zig"); + +const Pane = pane.Pane; +const TermIO = term.TermIO; + +var index: usize = 0; +var focus_list: std.ArrayList(*Pane) = undefined; +pub var current_focus: ?*Pane = null; + +pub fn init(allocator: std.mem.Allocator) !void { + focus_list = std.ArrayList(*Pane).init(allocator); +} + +pub fn deinit() void { + focus_list.deinit(); +} + +pub fn build_focus_list(top_pane: *Pane) !void { + focus_list.clearAndFree(); + try add_children(top_pane); + + if (current_focus) |current| { + for (focus_list.items, 0..) |item, i| { + if (item == current) { + index = i; + break; + } + } + } else if (focus_list.items.len != 0) { + index = 0; + current_focus = focus_list.items[index]; + current_focus.?.focused = true; + } +} + +fn add_children(p: *Pane) !void { + var iter = p.vtable.child_iterator(p); + while (iter.next()) |child| { + if (child.focusable) { + try focus_list.append(child); + } + + try add_children(child); + } +} + +pub fn focus_first(term_io: *TermIO, p: *Pane) !void { + if (p.focusable) { + try focus_pane(term_io, p); + return; + } + + try focus_first_child(term_io, p); +} + +fn focus_first_child(term_io: *TermIO, p: *Pane) !void { + var iter = p.vtable.child_iterator(p); + + while (iter.next()) |child| { + if (child.focusable) { + try focus_pane(term_io, child); + return; + } + + try focus_first_child(term_io, p); + } +} + +pub fn focus_next(term_io: *TermIO) void { + if (focus_list.items.len < 2) { + return; + } + + const new_index = (index + 1) % focus_list.items.len; + + focus_index(term_io, new_index); +} + +pub fn focus_prev(term_io: *TermIO) void { + if (focus_list.items.len < 2) { + return; + } + + var new_index = focus_list.items.len - 1; + if (index != 0) { + new_index = index - 1; + } + + focus_index(term_io, new_index); +} + +pub fn focus_pane(term_io: *TermIO, p: *Pane) !void { + for (focus_list.items, 0..) |item, i| { + if (item == p) { + focus_index(term_io, i); + return; + } + } +} + +pub fn focus_index(term_io: *TermIO, idx: usize) void { + if (idx > focus_list.items.len or idx == index) { + return; + } + + if (current_focus) |current| { + current.focus(term_io, false); + } + + index = idx; + current_focus = focus_list.items[index]; + current_focus.?.focus(term_io, true); +} diff --git a/src/main.zig b/src/main.zig index e5e8f3f..ea0c39c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,6 +11,7 @@ const line = @import("line.zig"); const tabs = @import("tabs.zig"); const stack = @import("stack.zig"); const text = @import("text.zig"); +const focus = @import("focus.zig"); var term_io: term.TermIO = undefined; var side_menu: menu.Menu = undefined; @@ -28,18 +29,26 @@ fn on_click() void { fn on_side_select(item: usize) void { log.debug("Side menu: Item {d} was selected", .{item}); if (item == 0) { - pane.focus(&m.pane, &term_io); + focus.focus_pane(&term_io, &m.pane) catch {}; } } fn on_select(item: usize) void { log.debug("Main menu: Item {d} was selected", .{item}); if (item == 0) { - pane.focus(&side_menu.pane, &term_io); + focus.focus_pane(&term_io, &side_menu.pane) catch {}; } } pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + defer { + if (gpa.deinit() == .leak) { + std.debug.print("Memory was leaked!! D:\n", .{}); + } + } + if (@import("builtin").mode == .Debug) { try log.init("panes.log", .Debug); } @@ -116,7 +125,7 @@ pub fn main() !void { }); pane.set_layout(&top.pane, &tabbar.pane); - try pane.init(&term_io); + try pane.init(allocator, &term_io); defer pane.cleanup(&term_io); while (!pane.should_exit.load(.acquire)) { diff --git a/src/menu.zig b/src/menu.zig index eed46b2..95a0ff7 100644 --- a/src/menu.zig +++ b/src/menu.zig @@ -71,25 +71,16 @@ pub const Menu = struct { fn update(pane_ptr: *Pane, term_io: *TermIO, key: term.Key) !bool { var self: *Menu = @fieldParentPtr("pane", pane_ptr); - if (self.index == self.items.len or key.type == .NO_KEY) { + if (self.index == self.items.len or key.key == .None) { return false; } - if (key.type == term.KeyType.SEQUENCE) { - const key_enum: term.SequenceKey = @enumFromInt(key.value); - switch (key_enum) { - term.SequenceKey.DOWN => try self.nextItem(term_io), - term.SequenceKey.UP => try self.prevItem(term_io), - else => return false, - } - } else { - switch (key.value) { - 13 => self.on_select(self.index), // Enter - 0x1b, 'q' => {}, // self.index = 0, - 'j' => try self.nextItem(term_io), - 'k' => try self.prevItem(term_io), - else => return false, - } + switch (key.key) { + .Return => self.on_select(self.index), + .Escape, .Q => {}, // self.index = 0, + .J, .Down => try self.nextItem(term_io), + .K, .Up => try self.prevItem(term_io), + else => return false, } return true; diff --git a/src/pane.zig b/src/pane.zig index e7d6453..c8728f9 100644 --- a/src/pane.zig +++ b/src/pane.zig @@ -4,13 +4,12 @@ const color = @import("colors.zig"); const dim = @import("dimensions.zig"); const stack = @import("stack.zig"); const term = @import("term.zig"); +const focus = @import("focus.zig"); const Border = @import("borders.zig").Border; const TermIO = term.TermIO; var window = Window{ .pane = Pane{}, .child = null }; -pub var focused_pane: *Pane = undefined; -pub var focused_index: usize = 1; pub var should_exit: std.atomic.Value(bool) = std.atomic.Value(bool).init(false); var needs_redraw: std.atomic.Value(bool) = std.atomic.Value(bool).init(false); var redraw_count: usize = 0; @@ -33,14 +32,12 @@ fn panic_signal(_: i32) callconv(.C) void { @panic("Segmentation fault!!"); } -pub fn set_layout(layout: *Pane, initial_focus: *Pane) void { +pub fn set_layout(layout: *Pane, _: *Pane) void { layout.parent = &window.pane; window.child = layout; - focused_pane = initial_focus; - focused_pane.focused = true; } -pub fn init(term_io: *TermIO) !void { +pub fn init(allocator: std.mem.Allocator, term_io: *TermIO) !void { term_io.enterRawMode(); term_io.saveScreen(); term_io.hideCursor(); @@ -67,6 +64,8 @@ pub fn init(term_io: *TermIO) !void { }; try std.posix.sigaction(std.posix.SIG.SEGV, &panic_handler, null); + try focus.init(allocator); + window.resize(term_io); try window.draw(term_io); } @@ -78,17 +77,23 @@ pub fn cleanup(term_io: *TermIO) void { // term_io.stdout.context.flush() catch @panic("Failed to flush buffered writer\n"); term_io.flush(); term_io.close(); + + focus.deinit(); } pub fn tick(term_io: *TermIO) !void { const key = term_io.getKey(false); - if (key.value == 113) { + if (key.key == .Q or key.key == .Escape) { should_exit.store(true, .release); return; } - if (key.type == .ASCII and key.value == '\t') { - cycle_focus(term_io); + if (key.key == .Tab) { + if (key.mod.shift) { + focus.focus_prev(term_io); + } else { + focus.focus_next(term_io); + } term_io.flush(); return; } @@ -104,38 +109,13 @@ pub fn tick(term_io: *TermIO) !void { log.debug("Resized {} times", .{redraw_count}); } - if (try focused_pane.update(term_io, key)) { - term_io.flush(); + if (focus.current_focus) |focused| { + if (try focused.update(term_io, key)) { + term_io.flush(); + } } } -pub fn cycle_focus(term_io: *TermIO) void { - // TODO: There must be a better way of doing this - - const parent_stack: *stack.Stack = @fieldParentPtr("pane", focused_pane.parent); - var index = (focused_index + 1) % parent_stack.children.len; - var to_focus = parent_stack.children[index].pane; - - while (!to_focus.focusable and index != focused_index) { - index = (index + 1) % parent_stack.children.len; - to_focus = parent_stack.children[index].pane; - } - - focus(to_focus, term_io); - focused_index = index; -} - -pub fn focus(to_focus: *Pane, term_io: *TermIO) void { - if (to_focus == focused_pane) return; - - focused_pane.focus(term_io, false); - - focused_pane = to_focus; - focused_index = 0; // TODO: Fix this - - to_focus.focus(term_io, true); -} - pub const Overflow = struct { x: enum { Wrap, Hidden }, y: enum { Scroll, Hidden }, @@ -166,6 +146,7 @@ pub const Pane = struct { } const PaneVtable = struct { + child_iterator: *const fn (*Pane) ChildIterator = ChildIterator.empty, draw: *const fn (*Pane, *TermIO) anyerror!void = defaultDraw, focus: *const fn (*Pane, *TermIO) anyerror!void = defaultFocus, resize: *const fn (*Pane) void = defaultResize, @@ -292,7 +273,6 @@ pub const Pane = struct { } pub fn focus(self: *Pane, term_io: *TermIO, focused: bool) void { - focused_pane = self; self.focused = focused; self.cursor = .{ .x = 0, .y = 0 }; @@ -361,6 +341,31 @@ pub const Pane = struct { } }; +pub const ChildIterator = struct { + parent: *Pane, + index: usize = 0, + vtable: VTable, + + const VTable = struct { + next: *const fn (self: *ChildIterator) ?*Pane, + }; + + pub fn next(self: *ChildIterator) ?*Pane { + return self.vtable.next(self); + } + + fn empty(_: *Pane) ChildIterator { + return ChildIterator{ + .parent = undefined, + .vtable = .{ .next = empty_next }, + }; + } + + fn empty_next(_: *ChildIterator) ?*Pane { + return null; + } +}; + const Window = struct { pane: Pane, child: ?*Pane, @@ -383,6 +388,7 @@ const Window = struct { fn draw(self: *Window, term_io: *TermIO) !void { if (self.child) |child| { + try focus.build_focus_list(child); try child.draw(term_io); term_io.flush(); } diff --git a/src/stack.zig b/src/stack.zig index d3e38d8..83a8f36 100644 --- a/src/stack.zig +++ b/src/stack.zig @@ -47,6 +47,7 @@ pub const Stack = struct { .focusable = false, .style = config.style, .vtable = .{ + .child_iterator = child_iterator, .resize = resize, .draw = draw, }, @@ -252,4 +253,22 @@ pub const Stack = struct { } } } + + fn child_iterator(self: *Pane) pane.ChildIterator { + return pane.ChildIterator{ + .parent = self, + .vtable = .{ .next = iter_next }, + }; + } }; + +fn iter_next(iter: *pane.ChildIterator) ?*Pane { + const stack: *Stack = @fieldParentPtr("pane", iter.parent); + + if (iter.index >= stack.children.len) { + return null; + } + + defer iter.index += 1; + return stack.children[iter.index].pane; +} diff --git a/src/tabs.zig b/src/tabs.zig index 069bfe8..ab69139 100644 --- a/src/tabs.zig +++ b/src/tabs.zig @@ -5,6 +5,7 @@ const menu = @import("menu.zig"); const pane = @import("pane.zig"); const term = @import("term.zig"); const std = @import("std"); +const focus = @import("focus.zig"); const Pane = pane.Pane; const Style = pane.Style; @@ -40,7 +41,7 @@ pub const TabBar = struct { .style = config.style, .vtable = .{ .draw = draw, - .focus = focus, + .focus = on_focus, .update = update, }, }; @@ -87,29 +88,8 @@ pub const TabBar = struct { fn update(pane_ptr: *pane.Pane, term_io: *TermIO, key: term.Key) !bool { var self: *TabBar = @fieldParentPtr("pane", pane_ptr); - if (key.type == .SEQUENCE) { - const seq: term.SequenceKey = @enumFromInt(key.value); - switch (seq) { - .LEFT => { - if (self.index > 0) { - self.index -= 1; - try draw(pane_ptr, term_io); - try self.config.target.update_content(self.tabs[self.index].pane, term_io); - return true; - } - }, - .RIGHT => { - self.index = @min(self.index + 1, self.tabs.len - 1); - try draw(pane_ptr, term_io); - try self.config.target.update_content(self.tabs[self.index].pane, term_io); - return true; - }, - else => {}, - } - } - - switch (key.value) { - 'h' => { + switch (key.key) { + .H, .Left => { if (self.index > 0) { self.index -= 1; try draw(pane_ptr, term_io); @@ -117,14 +97,14 @@ pub const TabBar = struct { return true; } }, - 'l' => { + .L, .Right => { self.index = @min(self.index + 1, self.tabs.len - 1); try draw(pane_ptr, term_io); try self.config.target.update_content(self.tabs[self.index].pane, term_io); return true; }, - '\r' => { - pane.focus(self.config.target.content, term_io); + .Return => { + try focus.focus_first(term_io, self.config.target.content); return true; }, else => {}, @@ -133,7 +113,7 @@ pub const TabBar = struct { return false; } - fn focus(pane_ptr: *Pane, term_io: *TermIO) !void { + fn on_focus(pane_ptr: *Pane, term_io: *TermIO) !void { try draw(pane_ptr, term_io); } }; @@ -146,6 +126,7 @@ pub const TabbedTarget = struct { mem.pane = Pane{ .style = style, .vtable = .{ + .child_iterator = child_iterator, .resize = resize, .draw = draw, }, @@ -175,4 +156,22 @@ pub const TabbedTarget = struct { try self.content.draw(term_io); } + + fn child_iterator(self: *Pane) pane.ChildIterator { + return pane.ChildIterator{ + .parent = self, + .vtable = .{ .next = iter_next }, + }; + } }; + +fn iter_next(iter: *pane.ChildIterator) ?*Pane { + const target: *TabbedTarget = @fieldParentPtr("pane", iter.parent); + + if (iter.index > 0) { + return null; + } + + iter.index += 1; + return target.content; +} diff --git a/src/term.zig b/src/term.zig index 78ef55e..2519d0f 100644 --- a/src/term.zig +++ b/src/term.zig @@ -27,9 +27,65 @@ pub const SequenceKey = enum(u8) { UNKNOWN, }; +pub const KeyCode = enum { + None, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Space, + Escape, + Tab, + Return, + Backspace, + Delete, + Up, + Down, + Left, + Right, +}; + +const Modifiers = packed struct { + ctrl: bool = false, + shift: bool = false, + alt: bool = false, +}; + pub const Key = struct { - type: KeyType, - value: u8, + key: KeyCode, + mod: Modifiers = .{}, }; pub const PrintFlags = struct { @@ -480,7 +536,7 @@ pub const TermIO = struct { if (!term_io.escape_sequence_queued) { const c = term_io.stdin.readByte() catch |err| { switch (err) { - error.WouldBlock, error.EndOfStream => if (!block) return .{ .type = KeyType.NO_KEY, .value = 0 }, + error.WouldBlock, error.EndOfStream => if (!block) return .{ .key = .None }, else => { log.err("{any}", .{err}); @panic("Unexpected error when reading from stdin"); @@ -488,15 +544,27 @@ pub const TermIO = struct { } continue; }; + if (c != 0x1b and sequence_len == 0) { - return .{ .type = KeyType.ASCII, .value = c }; + switch (c) { + 1...8, 10...12, 14...26 => return Key{ .key = @enumFromInt(@intFromEnum(KeyCode.A) + (c - 'a')), .mod = .{ .ctrl = true } }, + 'a'...'z' => return Key{ .key = @enumFromInt(@intFromEnum(KeyCode.A) + (c - 'a')) }, + 'A'...'Z' => return Key{ .key = @enumFromInt(@intFromEnum(KeyCode.A) + (c - 'A')), .mod = .{ .shift = true } }, + '0'...'9' => return Key{ .key = @enumFromInt(@intFromEnum(KeyCode.Zero) + (c - '0')) }, + ' ' => return Key{ .key = .Space }, + 27 => return Key{ .key = .Escape }, + '\t' => return Key{ .key = .Tab }, + '\r' => return Key{ .key = .Return }, + 127 => return Key{ .key = .Backspace }, + else => return Key{ .key = .None }, + } } } term_io.escape_sequence_queued = false; // Escape sequence sequence_len = 0; - escape_sequence[sequence_len] = term_io.stdin.readByte() catch return .{ .type = KeyType.ASCII, .value = 0x1b }; + escape_sequence[sequence_len] = term_io.stdin.readByte() catch return .{ .key = .Escape }; while (true) { sequence_len += 1; @@ -535,16 +603,18 @@ pub fn getTermIO(stdin: std.io.AnyReader, stdout: std.io.BufferedWriter(4096, st } fn decipherEscapeSequence(sequence: []u8) Key { + // I should probably make this better if (sequence.len == 2) { - if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'A' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.UP) }; - if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'B' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.DOWN) }; - if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'C' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.RIGHT) }; - if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'D' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.LEFT) }; + if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'A' })) return .{ .key = .Up }; + if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'B' })) return .{ .key = .Down }; + if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'C' })) return .{ .key = .Right }; + if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'D' })) return .{ .key = .Left }; + if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'Z' })) return .{ .key = .Tab, .mod = .{ .shift = true } }; } else if (sequence.len == 3) { - if (std.mem.eql(u8, sequence, &[3]u8{ '[', '3', '~' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.DELETE) }; + if (std.mem.eql(u8, sequence, &[3]u8{ '[', '3', '~' })) return .{ .key = .Delete }; } - return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.UNKNOWN) }; + return .{ .key = .None }; } test "stdout" {