add(example/elements): distinct different scrollable containers
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 46s
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 46s
This commit is contained in:
11
README.md
11
README.md
@@ -133,13 +133,18 @@ the primary use-case for myself to create this library in the first place.
|
||||
- [x] Button
|
||||
- [x] Text Input field
|
||||
- [ ] Popup-menu
|
||||
- [ ] Scrollable Content (i.e. show long text of an except of something and other smaller `Container`)
|
||||
- [ ] min size
|
||||
- [ ] mouse scrolling aware of mouse position (i.e. through multiple different scrollable `Container`)
|
||||
- [x] Scrollable Content (i.e. show long text of an except of something and other smaller `Container`)
|
||||
- [x] min size
|
||||
- [x] mouse scrolling aware of mouse position (i.e. through multiple different scrollable `Container`)
|
||||
- [ ] Launch sub-applications (not inside of a `Container` but during the application workflow, like an editor)
|
||||
- [ ] Styles
|
||||
- [ ] Text styles
|
||||
- [ ] Color palette
|
||||
- [ ] Error Handling
|
||||
- [ ] log and show error's without crashing the application
|
||||
- [ ] show application error that will cause a crash
|
||||
- [ ] Demo
|
||||
- [ ] use another tui application to launch and come back to (showcase the interrupt behavior)
|
||||
- [ ] Testability
|
||||
- [ ] snapshot ability to safe current screen (from `Renderer`) to test against
|
||||
- [ ] try to integrate them into the library itself such that they also serve as examples on how to test
|
||||
|
||||
@@ -23,7 +23,7 @@ pub const HelloWorldText = packed struct {
|
||||
// NOTE: error should only be returned here in case an in-recoverable exception has occurred
|
||||
const row = size.rows / 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.rows) + col;
|
||||
const anchor = (row * size.cols) + col;
|
||||
|
||||
for (0.., text) |idx, char| {
|
||||
cells[anchor + idx].style.fg = .black;
|
||||
@@ -52,7 +52,7 @@ pub fn main() !void {
|
||||
var element_wrapper: HelloWorldText = .{};
|
||||
const element = element_wrapper.element();
|
||||
|
||||
var box = try App.Container.init(allocator, .{
|
||||
var top_box = try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .blue },
|
||||
.layout = .{
|
||||
.gap = 1,
|
||||
@@ -61,21 +61,36 @@ pub fn main() !void {
|
||||
},
|
||||
.min_size = .{ .rows = 50 },
|
||||
}, .{});
|
||||
try box.append(try App.Container.init(allocator, .{
|
||||
try top_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .light_green },
|
||||
}, .{}));
|
||||
try box.append(try App.Container.init(allocator, .{
|
||||
try top_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .light_green },
|
||||
}, element));
|
||||
try box.append(try App.Container.init(allocator, .{
|
||||
try top_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .light_green },
|
||||
}, .{}));
|
||||
defer box.deinit();
|
||||
defer top_box.deinit();
|
||||
|
||||
var scrollable: App.Scrollable = .{
|
||||
.container = box,
|
||||
.vertical = true,
|
||||
};
|
||||
var bottom_box = try App.Container.init(allocator, .{
|
||||
.border = .{ .separator = .{ .enabled = true } },
|
||||
.rectangle = .{ .fill = .blue },
|
||||
.layout = .{
|
||||
.direction = .vertical,
|
||||
.padding = .vertical(1),
|
||||
},
|
||||
.min_size = .{ .rows = 30 },
|
||||
}, .{});
|
||||
try bottom_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .grey },
|
||||
}, .{}));
|
||||
try bottom_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .grey },
|
||||
}, element));
|
||||
try bottom_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .grey },
|
||||
}, .{}));
|
||||
defer bottom_box.deinit();
|
||||
|
||||
var container = try App.Container.init(allocator, .{
|
||||
.border = .{
|
||||
@@ -87,21 +102,17 @@ pub fn main() !void {
|
||||
.layout = .{
|
||||
.gap = 2,
|
||||
.padding = .all(5),
|
||||
.direction = .horizontal,
|
||||
.direction = .vertical,
|
||||
},
|
||||
}, .{});
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||
defer container.deinit();
|
||||
|
||||
try container.append(try App.Container.init(allocator, .{
|
||||
.border = .{
|
||||
.color = .light_blue,
|
||||
.sides = .vertical,
|
||||
},
|
||||
}, .{}));
|
||||
try container.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .blue },
|
||||
}, .{}));
|
||||
defer container.deinit(); // also de-initializes the children
|
||||
// place empty container containing the element of the scrollable Container.
|
||||
var scrollable_top: App.Scrollable = .init(top_box);
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable_top.element()));
|
||||
|
||||
var scrollable_bottom: App.Scrollable = .init(bottom_box);
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable_bottom.element()));
|
||||
|
||||
try app.start();
|
||||
defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err});
|
||||
@@ -112,37 +123,14 @@ pub fn main() !void {
|
||||
log.debug("received event: {s}", .{@tagName(event)});
|
||||
|
||||
switch (event) {
|
||||
.init => {
|
||||
try container.handle(event);
|
||||
continue; // do not render
|
||||
},
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.key => |key| {
|
||||
if (key.eql(.{ .cp = 'q' })) app.quit();
|
||||
|
||||
// corresponding element could even be changed from the 'outside' (not forced through the event system)
|
||||
// event system however allows for cross element communication (i.e. broadcasting messages, etc.)
|
||||
if (key.eql(.{ .cp = input.Escape })) element_wrapper.text_color = .black;
|
||||
|
||||
if (key.eql(.{ .cp = 'n', .mod = .{ .ctrl = true } })) {
|
||||
try app.interrupt();
|
||||
defer app.start() catch @panic("could not start app event loop");
|
||||
var child = std.process.Child.init(&.{"hx"}, allocator);
|
||||
_ = child.spawnAndWait() catch |err| app.postEvent(.{
|
||||
.err = .{
|
||||
.err = err,
|
||||
.msg = "Spawning $EDITOR failed",
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
// NOTE: errors could be displayed in another container in case one was received, etc. to provide the user with feedback
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
else => {},
|
||||
}
|
||||
|
||||
// NOTE: returned errors should be propagated back to the application
|
||||
container.handle(event) catch |err| app.postEvent(.{
|
||||
.err = .{
|
||||
.err = err,
|
||||
|
||||
@@ -91,10 +91,6 @@ pub fn Scrollable(Event: type) type {
|
||||
anchor: Position = .{},
|
||||
/// The actual container, that is scrollable.
|
||||
container: Container(Event),
|
||||
/// Enable horizontal scrolling. This also renders a scrollbar (along the bottom of the viewport).
|
||||
horizontal: bool = false,
|
||||
/// Enable vertical scrolling. This also renders a scrollbar (along the right of the viewport).
|
||||
vertical: bool = false,
|
||||
|
||||
pub fn element(this: *@This()) Element(Event) {
|
||||
return .{
|
||||
@@ -106,11 +102,13 @@ pub fn Scrollable(Event: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(container: Container(Event)) @This() {
|
||||
return .{ .container = container };
|
||||
}
|
||||
|
||||
fn handle(ctx: *anyopaque, event: Event) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
switch (event) {
|
||||
// TODO: emit `.resize` event for the container to set the size for the scrollable `Container`
|
||||
// - how would I determine the required or necessary `Size`?
|
||||
.resize => |size| {
|
||||
this.size = size;
|
||||
// TODO: scrollbar space - depending on configuration and only if necessary?
|
||||
@@ -124,17 +122,17 @@ pub fn Scrollable(Event: type) type {
|
||||
},
|
||||
// TODO: other means to scroll except with the mouse? (i.e. Ctrl-u/d, k/j, etc.?)
|
||||
.mouse => |mouse| switch (mouse.button) {
|
||||
Mouse.Button.wheel_up => if (this.vertical) {
|
||||
Mouse.Button.wheel_up => if (this.container_size.rows > this.size.rows) {
|
||||
this.anchor.row -|= 1;
|
||||
},
|
||||
Mouse.Button.wheel_down => if (this.vertical) {
|
||||
Mouse.Button.wheel_down => if (this.container_size.rows > this.size.rows) {
|
||||
const max_anchor_row = this.container_size.rows -| this.size.rows;
|
||||
this.anchor.row = @min(this.anchor.row + 1, max_anchor_row);
|
||||
},
|
||||
Mouse.Button.wheel_left => if (this.horizontal) {
|
||||
Mouse.Button.wheel_left => if (this.container_size.cols > this.size.cols) {
|
||||
this.anchor.col -|= 1;
|
||||
},
|
||||
Mouse.Button.wheel_right => if (this.horizontal) {
|
||||
Mouse.Button.wheel_right => if (this.container_size.cols > this.size.cols) {
|
||||
const max_anchor_col = this.container_size.cols -| this.size.cols;
|
||||
this.anchor.col = @min(this.anchor.col + 1, max_anchor_col);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user