Focus works now! :D

This commit is contained in:
Cameron Reed 2024-11-18 17:28:45 -07:00
parent 8ae8ce9ac7
commit e1da270181
8 changed files with 307 additions and 98 deletions

View File

@ -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();
}

115
src/focus.zig Normal file
View File

@ -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);
}

View File

@ -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)) {

View File

@ -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;

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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" {