diff --git a/README.md b/README.md index 9365c41..69fbccf 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,6 @@ const zterm: *Dependency = b.dependency("zterm", .{ .target = target, .optimize = optimize, }); -// ... -exe.root_module.addImport("zterm", zterm.module("zterm")); ``` ### Documentation diff --git a/src/element.zig b/src/element.zig index 75c3af1..d96d5ad 100644 --- a/src/element.zig +++ b/src/element.zig @@ -267,8 +267,8 @@ pub fn Scrollable(Model: type, Event: type) type { this.size = size; if (this.configuration.scrollbar) { - if (this.container.properties.size.dim.x > this.size.x or this.container.size.x > this.size.x) this.configuration.y_axis = true; - if (this.container.properties.size.dim.y > this.size.y or this.container.size.y > this.size.y) this.configuration.x_axis = true; + this.configuration.y_axis = this.container.properties.size.dim.x > this.size.x or this.container.size.x > this.size.x; + this.configuration.x_axis = this.container.properties.size.dim.y > this.size.y or this.container.size.y > this.size.y; const min_size = this.container.element.minSize(size); this.container.resize(.{ @@ -295,7 +295,43 @@ pub fn Scrollable(Model: type, Event: type) type { fn handle(ctx: *anyopaque, model: *Model, event: Event) !void { const this: *@This() = @ptrCast(@alignCast(ctx)); switch (event) { - // TODO other means to scroll except with the mouse? (i.e. Ctrl-u/d, k/j, etc.?) + .init => this.container.resize(this.container.element.minSize(this.size)), + // TODO what about multiple scrollable `Element` usages? mouse + // can differ between them (due to the event being only send to + // the `Container` / `Element` one under the cursor!) + .key => |key| { + if (key.eql(.{ .cp = 'j' }) or key.eql(.{ .cp = input.Down })) { + const max_anchor_y = this.container_size.y -| this.size.y; + this.anchor.y = @min(this.anchor.y + 1, max_anchor_y); + } + if (key.eql(.{ .cp = 'k' }) or key.eql(.{ .cp = input.Up })) { + this.anchor.y -|= 1; + } + if (key.eql(.{ .cp = 'd', .mod = .{ .ctrl = true } })) { + // half page down + const half_page = this.size.y / 2; + const max_anchor_y = this.container_size.y -| this.size.y; + this.anchor.y = @min(this.anchor.y + half_page, max_anchor_y); + } + if (key.eql(.{ .cp = 'u', .mod = .{ .ctrl = true } })) { + // half page up + const half_page = this.size.y / 2; + this.anchor.y -|= half_page; + } + if (key.eql(.{ .cp = 'f', .mod = .{ .ctrl = true } }) or key.eql(.{ .cp = input.PageDown })) { + const max_anchor_y = this.container_size.y -| this.size.y; + this.anchor.y = @min(this.anchor.y + this.size.y, max_anchor_y); + } + if (key.eql(.{ .cp = 'b', .mod = .{ .ctrl = true } }) or key.eql(.{ .cp = input.PageDown })) { + this.anchor.y -|= this.size.y; + } + if (key.eql(.{ .cp = 'g' }) or key.eql(.{ .cp = input.Home })) { + this.anchor.y = 0; + } + if (key.eql(.{ .cp = 'G' }) or key.eql(.{ .cp = input.End })) { + this.anchor.y = this.container_size.y -| this.size.y; + } + }, .mouse => |mouse| switch (mouse.button) { Mouse.Button.wheel_up => if (this.container_size.y > this.size.y) { this.anchor.y -|= 1; @@ -1073,8 +1109,8 @@ test "scrollable vertical" { try renderer.render(@TypeOf(container), &container, Model, &.{}); try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.top.zon"), renderer.screen); - // scroll down 15 times (exactly to the end) - for (0..15) |_| try container.handle(&model, .{ + // scroll down 15 times (exactly to the end) (with both mouse and key inputs) + for (0..7) |_| try container.handle(&model, .{ .mouse = .{ .button = .wheel_down, .kind = .press, @@ -1082,6 +1118,9 @@ test "scrollable vertical" { .y = 5, }, }); + for (7..15) |_| try container.handle(&model, .{ + .key = .{ .cp = 'j' }, + }); try renderer.render(@TypeOf(container), &container, Model, &.{}); try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen); @@ -1096,6 +1135,40 @@ test "scrollable vertical" { }); try renderer.render(@TypeOf(container), &container, Model, &.{}); try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen); + + // scrolling up and down again + for (0..5) |_| try container.handle(&model, .{ + .key = .{ .cp = 'k' }, + }); + for (0..5) |_| try container.handle(&model, .{ + .key = .{ .cp = 'j' }, + }); + try renderer.render(@TypeOf(container), &container, Model, &.{}); + try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen); + + // scrolling half page up and down again + try container.handle(&model, .{ + .key = .{ .cp = 'u', .mod = .{ .ctrl = true } }, + }); + try container.handle(&model, .{ + .key = .{ .cp = 'd', .mod = .{ .ctrl = true } }, + }); + try renderer.render(@TypeOf(container), &container, Model, &.{}); + try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen); + + // scrolling page up + try container.handle(&model, .{ + .key = .{ .cp = 'b', .mod = .{ .ctrl = true } }, + }); + try renderer.render(@TypeOf(container), &container, Model, &.{}); + try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.top.zon"), renderer.screen); + + // and down again + try container.handle(&model, .{ + .key = .{ .cp = 'f', .mod = .{ .ctrl = true } }, + }); + try renderer.render(@TypeOf(container), &container, Model, &.{}); + try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen); } test "scrollable vertical with scrollbar" {