doc(Elements): update and stateful Element implementations

2025-10-26 18:19:05 +01:00
parent 600846a92b
commit fc53243afd

@@ -8,7 +8,7 @@ Currently the only library provided `Element` implementation is the `Scrollable`
Contents that is scrollable should be done *virtually* through the contents of the `Container`. This means each container contents implements scrolling for itself if required. Provided with a minimal size option the corresponding container becomes scrollable if the actual containing `Container` is too small to hold the minimal size. Otherwise the `Container` is not scrollable (as it is not necessary). With this the `Container` is dynamically scrollable if necessary (i.e. if the screen size changes, etc.).
Known issues:
**Known issues:**
- The scrolling input action would then also be implemented by the user, but how given that some if the user input is already handled (i.e. scrolling with the mouse).
- *To be implemented:* Automatic rendering of a scrollbar (vertical and/or horizontal - according to the container size and the virtual size).
@@ -20,39 +20,62 @@ The following snippet shows a template for an `Element` implementation:
```zig
/// This is an empty template implementation for an `Element` type.
pub fn Template(Event: type) type {
return packed struct {
pub fn element(this: *@This()) Element(Event) {
pub fn Template(Model: type, Event: type) type {
// *Model* should be `App.Model`
// *Event* should be `App.Event`
return struct {
pub fn element(this: *@This()) App.Element {
return .{
.ptr = this,
.vtable = &.{
.resize = resize,
.reposition = reposition,
.handle = handle,
.content = content,
},
};
}
fn handle(ctx: *anyopaque, event: Event) !void {
fn resize(ctx: *anyopaque, size: Point) void {
const this: *@This() = @ptrCast(@alignCast(ctx));
_ = this;
_ = size;
}
fn reposition(ctx: *anyopaque, origin: Point) void {
const this: *@This() = @ptrCast(@alignCast(ctx));
_ = this;
_ = origin;
}
fn handle(ctx: *anyopaque, model: *Model, event: Event) !void {
const this: *@This() = @ptrCast(@alignCast(ctx));
_ = this;
_ = model;
switch (event) {
else => {},
}
}
fn content(ctx: *anyopaque, cells: []Cell, size: Size) !void {
fn content(ctx: *anyopaque, model: *const Model, cells: []Cell, size: Size) !void {
const this: *@This() = @ptrCast(@alignCast(ctx));
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = this;
_ = model;
}
};
}
```
For an example implementations of `Element` types, please refer to [examples](Examples.md).
> [!TIP]
> For example implementations of `Element` types, please refer to [examples](Examples.md).
### Tips
Only provide function callbacks for the `Element` implementation if the callback is necessary for the functionality, otherwise do not provide a callback function in the *vtable* when creating an `Element` instance of the implementation.
The `element` initializer function should be the only public function of the `Element` implementation, as the cast of the `*anyopaque` pointer will provide you will an instance of the specific implementation, giving you access to the private functions, members, etc. anyway.
The documentation contains tips about how to implement corresponding event loops or how to design own `Element`s. And also on how to test accordingly and use the library itself for examples on how to design the test cases.
### User specific event handling and content rendering
@@ -61,3 +84,19 @@ For interactions controlled by the user each container can use an `Element` inte
Composing multiple `Element`s currently requires the implementation of a wrapper which contains the `Element`s that need to be handled (should work pretty well for stateless `Element`s). Such *stateless* `Element`s may be provided by this library.
### Stateful `Element`s
You can capture state in two different places:
1. On a per `Element` instance basis through *member* variables of a given instance.
2. In the `Model` that is shared accross the entire application.
State using the first idea should be keept at a minimum and used if they are specific to only that `Element` implementation and no others in your application.
State that shall be shared between other `Element`s in your application should propagate their state to the `Model`.
> [!WARN]
> However keep in might that this might cause side-effects (i.e. having the same `Element` implementation instanciated multiple times in the same `Container` tree, might cause the `handle` callback to be called multiple times in a single event loop iteration).
> [!INFO]
> This exact seperatation is also used in android app development using [Jetpack compose](https://developer.android.com/compose). Common patterns, best practices, etc. may also apply for *zterm*.