WIP: use viewport to allow sizes of scroll to extend further than renderable screen
This commit is contained in:
@@ -7,7 +7,7 @@ const mergeTaggedUnions = event.mergeTaggedUnions;
|
||||
const isTaggedUnion = event.isTaggedUnion;
|
||||
|
||||
const Key = @import("key.zig");
|
||||
const Size = @import("size.zig");
|
||||
const Size = @import("size.zig").Size;
|
||||
const Queue = @import("queue.zig").Queue;
|
||||
|
||||
const log = std.log.scoped(.app);
|
||||
|
||||
@@ -4,6 +4,7 @@ const Style = @import("style.zig");
|
||||
pub const Cell = @This();
|
||||
|
||||
style: Style = .{ .attributes = &.{} },
|
||||
// TODO: embrace `zg` dependency more due to utf-8 encoding
|
||||
cp: u21 = ' ',
|
||||
|
||||
pub fn eql(this: Cell, other: Cell) bool {
|
||||
|
||||
@@ -4,13 +4,13 @@ const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
||||
|
||||
const Cell = @import("cell.zig");
|
||||
const Color = @import("color.zig").Color;
|
||||
const Size = @import("size.zig");
|
||||
const Size = @import("size.zig").Size;
|
||||
const Style = @import("style.zig");
|
||||
|
||||
const log = std.log.scoped(.container);
|
||||
|
||||
/// Border configuration struct
|
||||
pub const Border = struct {
|
||||
pub const Border = packed struct {
|
||||
pub const rounded_border: [6]u21 = .{ '╭', '─', '╮', '│', '╰', '╯' };
|
||||
pub const squared_border: [6]u21 = .{ '┌', '─', '┐', '│', '└', '┘' };
|
||||
/// Color to use for the border
|
||||
@@ -40,10 +40,10 @@ pub const Border = struct {
|
||||
}
|
||||
} = .{},
|
||||
/// Configure separator borders between child element to added to the layout
|
||||
separator: struct {
|
||||
separator: packed struct {
|
||||
enabled: bool = false,
|
||||
color: Color = .white,
|
||||
line: enum {
|
||||
line: enum(u1) {
|
||||
line,
|
||||
dotted,
|
||||
// TODO: add more variations which could be used for the separator
|
||||
@@ -52,7 +52,7 @@ pub const Border = struct {
|
||||
|
||||
// NOTE: caller owns `cells` slice and ensures that `cells.len == size.cols * size.rows`
|
||||
pub fn contents(this: @This(), cells: []Cell, size: Size, layout: Layout, len: u16) void {
|
||||
std.debug.assert(cells.len == size.cols * size.rows);
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
|
||||
const frame = switch (this.corners) {
|
||||
.rounded => Border.rounded_border,
|
||||
@@ -62,7 +62,7 @@ pub const Border = struct {
|
||||
|
||||
// render top and bottom border
|
||||
for (0..size.cols) |col| {
|
||||
const last_row = (size.rows - 1) * size.cols;
|
||||
const last_row = @as(usize, size.rows - 1) * @as(usize, size.cols);
|
||||
if (this.sides.left and col == 0) {
|
||||
// top left corner
|
||||
if (this.sides.top) cells[col].cp = frame[0];
|
||||
@@ -170,7 +170,7 @@ pub const Border = struct {
|
||||
};
|
||||
|
||||
/// Rectangle configuration struct
|
||||
pub const Rectangle = struct {
|
||||
pub const Rectangle = packed struct {
|
||||
/// `Color` to use to fill the `Rectangle` with
|
||||
/// NOTE: used as background color when rendering! such that it renders the
|
||||
/// children accordingly without removing the coloring of the `Rectangle`
|
||||
@@ -178,7 +178,7 @@ pub const Rectangle = struct {
|
||||
|
||||
// NOTE: caller owns `cells` slice and ensures that `cells.len == size.cols * size.rows`
|
||||
pub fn contents(this: @This(), cells: []Cell, size: Size) void {
|
||||
std.debug.assert(cells.len == size.cols * size.rows);
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
|
||||
for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
@@ -200,8 +200,21 @@ pub const Scroll = packed struct {
|
||||
|
||||
// TODO: rendering enhancements:
|
||||
// - render corresponding scroll-bars?
|
||||
|
||||
// TODO: does the `size` then actually need to be part of the `Scroll`, as the user should not tamper with the value anyway..
|
||||
|
||||
// TODO: should the container size be 'virtual' i.e. the complete
|
||||
// - content size (which might be larger than the available screen)
|
||||
// but how would a scrollable small portion of the screen work?
|
||||
// -> this means that the size can remain and have its meaning, but the
|
||||
// content needs another abstraction for the scroll
|
||||
size: Size = .{},
|
||||
// anchor => position in view of scrollable contents
|
||||
// cols / rows => view port dimensions
|
||||
// render => window in window
|
||||
};
|
||||
|
||||
// TODO: can `Layout` become `packed`? -> for that the sizing cannot be a tagged enum!
|
||||
/// Layout configuration struct
|
||||
pub const Layout = struct {
|
||||
/// control the direction in which child elements are laid out
|
||||
@@ -240,8 +253,8 @@ pub const Layout = struct {
|
||||
// NOTE: `sizing` cannot be *packed* because of the tagged unions? is this necessary -> I would need to measure the size differences
|
||||
/// Sizing to be used for the width and height of this element to use
|
||||
sizing: struct {
|
||||
width: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
|
||||
height: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
|
||||
width: union(enum(u2)) { fit, grow, fixed: u16, percent: u16 } = .fit,
|
||||
height: union(enum(u2)) { fit, grow, fixed: u16, percent: u16 } = .fit,
|
||||
} = .{},
|
||||
};
|
||||
|
||||
@@ -251,7 +264,8 @@ pub fn Container(comptime Event: type) type {
|
||||
}
|
||||
return struct {
|
||||
allocator: std.mem.Allocator,
|
||||
size: Size,
|
||||
/// Size of actual columns and rows used to render this `Container`
|
||||
viewport: Size,
|
||||
properties: Properties,
|
||||
elements: std.ArrayList(@This()),
|
||||
|
||||
@@ -268,7 +282,7 @@ pub fn Container(comptime Event: type) type {
|
||||
pub fn init(allocator: std.mem.Allocator, properties: Properties) !@This() {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.size = .{ .cols = 0, .rows = 0 },
|
||||
.viewport = .{},
|
||||
.properties = properties,
|
||||
.elements = std.ArrayList(@This()).init(allocator),
|
||||
};
|
||||
@@ -289,8 +303,16 @@ pub fn Container(comptime Event: type) type {
|
||||
switch (event) {
|
||||
.init => log.debug(".init event", .{}),
|
||||
.resize => |s| resize: {
|
||||
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
s.anchor.col,
|
||||
s.anchor.row,
|
||||
s.cols,
|
||||
s.rows,
|
||||
});
|
||||
this.viewport = s;
|
||||
// sizing
|
||||
var size = s;
|
||||
size.anchor = .{}; // reset relative anchor of size!
|
||||
const sizing = this.properties.layout.sizing;
|
||||
switch (sizing.width) {
|
||||
.fit => {
|
||||
@@ -301,7 +323,7 @@ pub fn Container(comptime Event: type) type {
|
||||
// NOTE: this is pretty much the current implementation
|
||||
},
|
||||
.fixed => |fix| {
|
||||
std.debug.assert(fix <= size.cols);
|
||||
// NOTE: fixed may now even define a larger column / row span for a container (but not everything might be rendered)
|
||||
size.cols = fix;
|
||||
},
|
||||
.percent => |percent| {
|
||||
@@ -312,13 +334,16 @@ pub fn Container(comptime Event: type) type {
|
||||
switch (sizing.height) {
|
||||
.fit => {
|
||||
// use as much space as necessary (but nothing more than necessary)
|
||||
// TODO: I need to work out the representation between the
|
||||
// - `Size` and `Size.Position` with the actual screen size
|
||||
// - the virtual screen may be larger than the actual screen (can I do this?)
|
||||
},
|
||||
.grow => {
|
||||
// grow use as much space as available by the parent (i.e. the entire width)
|
||||
// NOTE: this is pretty much the current implementation
|
||||
},
|
||||
.fixed => |fix| {
|
||||
std.debug.assert(fix <= size.rows);
|
||||
// NOTE: fixed may now even define a larger column / row span for a container (but not everything might be rendered)
|
||||
size.rows = fix;
|
||||
},
|
||||
.percent => |percent| {
|
||||
@@ -326,14 +351,7 @@ pub fn Container(comptime Event: type) type {
|
||||
size.rows = @divTrunc(size.rows * percent, 100);
|
||||
},
|
||||
}
|
||||
|
||||
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
size.anchor.col,
|
||||
size.anchor.row,
|
||||
size.cols,
|
||||
size.rows,
|
||||
});
|
||||
this.size = size;
|
||||
this.properties.scroll.size = size;
|
||||
|
||||
if (this.elements.items.len == 0) break :resize;
|
||||
|
||||
@@ -446,10 +464,51 @@ pub fn Container(comptime Event: type) type {
|
||||
}
|
||||
|
||||
pub fn contents(this: *const @This()) ![]const Cell {
|
||||
const cells = try this.allocator.alloc(Cell, this.size.cols * this.size.rows);
|
||||
@memset(cells, .{}); // reset all cells
|
||||
this.properties.border.contents(cells, this.size, this.properties.layout, @truncate(this.elements.items.len));
|
||||
this.properties.rectangle.contents(cells, this.size);
|
||||
const content = try this.allocator.alloc(Cell, @as(usize, this.properties.scroll.size.cols) * @as(usize, this.properties.scroll.size.rows));
|
||||
defer this.allocator.free(content);
|
||||
@memset(content, .{});
|
||||
|
||||
const cells = try this.allocator.alloc(Cell, @as(usize, this.viewport.cols) * @as(usize, this.viewport.rows));
|
||||
@memset(cells, .{});
|
||||
|
||||
this.properties.border.contents(content, this.properties.scroll.size, this.properties.layout, @truncate(this.elements.items.len));
|
||||
this.properties.rectangle.contents(content, this.properties.scroll.size);
|
||||
|
||||
const cols = blk: {
|
||||
var cols: u16 = this.properties.scroll.size.cols;
|
||||
if (this.properties.scroll.size.cols > this.viewport.cols) {
|
||||
cols = this.viewport.cols;
|
||||
}
|
||||
break :blk cols;
|
||||
};
|
||||
const rows = blk: {
|
||||
var rows: u16 = this.properties.scroll.size.rows;
|
||||
if (this.properties.scroll.size.rows > this.viewport.rows) {
|
||||
rows = this.viewport.rows;
|
||||
}
|
||||
break :blk rows;
|
||||
};
|
||||
const anchor = (this.properties.scroll.size.anchor.row * this.properties.scroll.size.cols) + this.properties.scroll.size.anchor.col;
|
||||
log.debug("viewport = .{{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
this.viewport.anchor.col,
|
||||
this.viewport.anchor.row,
|
||||
this.viewport.cols,
|
||||
this.viewport.rows,
|
||||
});
|
||||
log.debug("size = .{{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
this.properties.scroll.size.anchor.col,
|
||||
this.properties.scroll.size.anchor.row,
|
||||
this.properties.scroll.size.cols,
|
||||
this.properties.scroll.size.rows,
|
||||
});
|
||||
for (0..rows) |row| {
|
||||
for (0..cols) |col| {
|
||||
// TODO: try to do this with @memcpy instead to improve performance
|
||||
const cell = content[anchor + (row * this.properties.scroll.size.cols) + col];
|
||||
cells[row * this.viewport.cols + col].cp = cell.cp;
|
||||
cells[row * this.viewport.cols + col].style = cell.style;
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const std = @import("std");
|
||||
const terminal = @import("terminal.zig");
|
||||
|
||||
const Size = @import("size.zig");
|
||||
const Size = @import("size.zig").Size;
|
||||
const Key = @import("key.zig");
|
||||
|
||||
/// System events available to every `zterm.App`
|
||||
|
||||
@@ -2,8 +2,8 @@ const std = @import("std");
|
||||
const terminal = @import("terminal.zig");
|
||||
|
||||
const Cell = @import("cell.zig");
|
||||
const Size = @import("size.zig");
|
||||
const Position = Size.Position;
|
||||
const Position = @import("size.zig").Position;
|
||||
const Size = @import("size.zig").Size;
|
||||
|
||||
/// Double-buffered intermediate rendering pipeline
|
||||
pub const Buffered = struct {
|
||||
@@ -62,17 +62,17 @@ pub const Buffered = struct {
|
||||
|
||||
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
||||
pub fn render(this: *@This(), comptime T: type, container: *T) !void {
|
||||
const size: Size = container.size;
|
||||
const viewport: Size = container.viewport;
|
||||
const cells: []const Cell = try container.contents();
|
||||
|
||||
if (cells.len == 0) return;
|
||||
|
||||
var idx: usize = 0;
|
||||
var vs = this.virtual_screen;
|
||||
const anchor = (size.anchor.row * this.size.cols) + size.anchor.col;
|
||||
const anchor = (viewport.anchor.row * this.size.cols) + viewport.anchor.col;
|
||||
|
||||
blk: for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
blk: for (0..viewport.rows) |row| {
|
||||
for (0..viewport.cols) |col| {
|
||||
const cell = cells[idx];
|
||||
idx += 1;
|
||||
|
||||
|
||||
12
src/size.zig
12
src/size.zig
@@ -1,10 +1,10 @@
|
||||
pub const Size = @This();
|
||||
pub const Size = packed struct {
|
||||
anchor: Position = .{},
|
||||
cols: u16 = 0,
|
||||
rows: u16 = 0,
|
||||
};
|
||||
|
||||
pub const Position = struct {
|
||||
pub const Position = packed struct {
|
||||
col: u16 = 0,
|
||||
row: u16 = 0,
|
||||
};
|
||||
|
||||
anchor: Position = .{},
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
|
||||
@@ -2,7 +2,8 @@ const std = @import("std");
|
||||
pub const code_point = @import("code_point");
|
||||
|
||||
const Key = @import("key.zig");
|
||||
const Size = @import("size.zig");
|
||||
const Position = @import("size.zig").Position;
|
||||
const Size = @import("size.zig").Size;
|
||||
const Cell = @import("cell.zig");
|
||||
|
||||
const log = std.log.scoped(.terminal);
|
||||
@@ -78,7 +79,7 @@ pub fn writer() Writer {
|
||||
return .{ .context = .{} };
|
||||
}
|
||||
|
||||
pub fn setCursorPosition(pos: Size.Position) !void {
|
||||
pub fn setCursorPosition(pos: Position) !void {
|
||||
var buf: [64]u8 = undefined;
|
||||
const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.row, pos.col });
|
||||
_ = try std.posix.write(std.posix.STDIN_FILENO, value);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// private imports
|
||||
const container = @import("container.zig");
|
||||
const color = @import("color.zig");
|
||||
const size = @import("size.zig");
|
||||
|
||||
// public exports
|
||||
pub const App = @import("app.zig").App;
|
||||
pub const Renderer = @import("render.zig");
|
||||
|
||||
@@ -10,18 +14,19 @@ pub const Scroll = container.Scroll;
|
||||
pub const Layout = container.Layout;
|
||||
|
||||
pub const Cell = @import("cell.zig");
|
||||
pub const Color = @import("color.zig").Color;
|
||||
pub const Color = color.Color;
|
||||
pub const Key = @import("key.zig");
|
||||
pub const Size = @import("size.zig");
|
||||
pub const Size = size.Size;
|
||||
pub const Style = @import("style.zig");
|
||||
|
||||
test {
|
||||
_ = @import("terminal.zig");
|
||||
_ = @import("queue.zig");
|
||||
|
||||
_ = color;
|
||||
_ = size;
|
||||
|
||||
_ = Cell;
|
||||
_ = Color;
|
||||
_ = Key;
|
||||
_ = Size;
|
||||
_ = Style;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user