feat: ui improvements

This commit is contained in:
2025-06-06 16:08:51 +02:00
parent 1fc509f897
commit 3e3f03f11a
4 changed files with 200 additions and 65 deletions

View File

@@ -37,8 +37,8 @@
// internet connectivity.
.dependencies = .{
.vaxis = .{
.url = "git+https://github.com/rockorager/libvaxis.git#ae71b6545c099e73d45df7e5dd7c7a3081839468",
.hash = "vaxis-0.1.0-BWNV_JEMCQBskZQsnlzh6GoyHSDgOi41bCoZIB2pW-E7",
.url = "git+https://github.com/ADIX7/libvaxis.git#cffbf11410f81ed1a906f08afd3748bcd4e73506",
.hash = "vaxis-0.1.0-BWNV_PURCQAg2xZD0vlf6vOBOhDQ3hY53MP9tn0EybNA",
},
.dvui = .{
.url = "git+https://github.com/david-vanderson/dvui.git#5703f8f02fb8e1bf2c866517f208745e4d7af869",

View File

@@ -0,0 +1,50 @@
pub const ContextModifier = struct {
child: vxfw.Widget,
modifiers: union(enum) {
single: *const fn (ctx: vxfw.DrawContext) vxfw.DrawContext,
multi: []*const fn (ctx: vxfw.DrawContext) vxfw.DrawContext,
},
fn drawFn(obj: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface {
const self: *ContextModifier = @ptrCast(@alignCast(obj));
const modified_ctx = switch (self.modifiers) {
.single => |modifier| modifier(ctx),
.multi => |modifiers| blk: {
var wip_ctx = ctx;
for (modifiers) |modifier| {
wip_ctx = modifier(wip_ctx);
}
break :blk wip_ctx;
},
};
return self.child.draw(modified_ctx);
}
pub fn widget(self: *ContextModifier) vxfw.Widget {
return vxfw.Widget{
.userdata = @ptrCast(@alignCast(self)),
.drawFn = drawFn,
};
}
};
pub fn fromWidget(widget: vxfw.Widget, modifier: *const fn (ctx: vxfw.DrawContext) vxfw.DrawContext, arena: std.mem.Allocator) !vxfw.Widget {
var context_modifier = try arena.create(ContextModifier);
context_modifier.* = ContextModifier{
.child = widget,
.modifiers = .{ .single = modifier },
};
return context_modifier.widget();
}
pub fn maxHeight1(ctx: vxfw.DrawContext) vxfw.DrawContext {
return ctx.withConstraints(
ctx.min,
.{
.width = ctx.max.width,
.height = 1,
},
);
}
const std = @import("std");
const vaxis = @import("vaxis");
const vxfw = vaxis.vxfw;

View File

@@ -66,6 +66,14 @@ const Model = struct {
}
fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface {
// These are for debugging instant crashes
// std.debug.print(vaxis.ctlseqs.rmcup, .{});
// std.debug.print(vaxis.ctlseqs.show_cursor, .{});
// std.debug.print(vaxis.ctlseqs.sgr_reset, .{});
// std.debug.print(vaxis.ctlseqs.erase_below_cursor, .{});
// std.debug.print(vaxis.ctlseqs.in_band_resize_reset, .{});
// std.debug.print(vaxis.ctlseqs.mouse_reset, .{});
const vm: *Model = @ptrCast(@alignCast(ptr));
vm.redraw = false;
@@ -234,45 +242,58 @@ const Model = struct {
break :blk models.FullName{ .path = try ctx.arena.dupe(u8, currentItem.value.*.fullName.path) };
} else null;
const widgets = try createItems(ctx, &currentTab.currentItems, current_full_name);
if (widgets) |w| {
vm.current_items_view.children.slice = w;
} else {
vm.current_items_view.children.slice = &.{};
}
updateList(
vm.current_items_view,
try createItems(ctx, &currentTab.currentItems, current_full_name),
);
}
fn createParentItems(ctx: vxfw.DrawContext, vm: *Model) !void {
const currentTab = vm.app_common_model.appState.currentTab;
//TODO: current fullname
const widgets = try createItems(ctx, &currentTab.currentParentItems, null);
if (widgets) |w| {
vm.parent_items_view.children.slice = w;
}
updateList(
vm.parent_items_view,
try createItems(ctx, &currentTab.currentParentItems, null),
);
}
fn createChildrenItems(ctx: vxfw.DrawContext, vm: *Model) !void {
const currentTab = vm.app_common_model.appState.currentTab;
//TODO: current fullname
const widgets = try createItems(ctx, &currentTab.currentChildren, null);
if (widgets) |w| {
vm.current_children_view.children.slice = w;
updateList(
vm.current_children_view,
try createItems(ctx, &currentTab.currentChildren, null),
);
}
fn updateList(
listView: *vxfw.ListView,
result: ?struct { []vxfw.Widget, u32 },
) void {
if (result) |r| {
const widgets, const index = r;
listView.children.slice = widgets;
listView.cursor = index;
listView.ensureScroll();
} else {
listView.children.slice = &.{};
}
}
fn createItems(
ctx: vxfw.DrawContext,
locked_items: *locked(?std.ArrayList(Arc(*models.Item))),
current_full_name: ?models.FullName,
) !?[]vxfw.Widget {
const text_items = blk: {
) !?struct { []vxfw.Widget, u32 } {
locked_items.mutex.lock();
defer locked_items.mutex.unlock();
const items = locked_items.data orelse return null;
const children = try ctx.arena.create(std.ArrayList(*vxfw.Text));
children.* = std.ArrayList(*vxfw.Text).init(ctx.arena);
const children = try ctx.arena.create(std.ArrayList(vxfw.Widget));
children.* = .init(ctx.arena);
for (items.items) |*original_arc_child| {
var active_item_index: u32 = 0;
for (items.items, 0..) |*original_arc_child, item_index| {
const arc_child = original_arc_child.retain();
defer if (arc_child.releaseUnwrap()) |item| item.deinit();
@@ -282,6 +303,10 @@ const Model = struct {
else
false;
if (is_active) {
active_item_index = @intCast(item_index);
}
const fg, const bg = colors: {
var fg: vaxis.Color = .default;
var bg: vaxis.Color = .default;
@@ -304,30 +329,89 @@ const Model = struct {
break :colors .{ fg, bg };
};
//NOTE: the right padding is wrong, if the text is too long, the remainder of the text and also the space will be clipped
const text = try std.fmt.allocPrint(ctx.arena, " {s} ", .{child.displayName});
const text_element = try ctx.arena.create(vxfw.Text);
text_element.* = vxfw.Text{
.text = text,
.overflow = .clip,
var item_children_list = std.ArrayList(vxfw.FlexItem).init(ctx.arena);
{
const display_name_text = try ctx.arena.dupe(u8, child.displayName);
const display_name_text_element = try ctx.arena.create(vxfw.Text);
display_name_text_element.* = vxfw.Text{
.text = display_name_text,
// .padding = .{ .left = 1, .right = 1 },
.softwrap = false,
.overflow = .ellipsis,
.style = .{
.bg = bg,
.fg = fg,
},
};
try children.append(text_element);
const padding = try ctx.arena.create(vxfw.Padding);
padding.* = .{
.child = display_name_text_element.widget(),
.padding = vxfw.Padding.horizontal(1),
.fill = .{
.style = .{
.bg = bg,
.fg = fg,
},
},
};
try item_children_list.append(.{
.widget = padding.widget(),
.flex = 1,
});
}
break :blk children;
if (child.item == .container) {
const container = child.item.container;
container.children.mutex.lock();
defer container.children.mutex.unlock();
const container_child_number = container.children.data.items.len;
const container_child_number_text = try std.fmt.allocPrint(ctx.arena, "{}", .{container_child_number});
const container_child_number_element = try ctx.arena.create(vxfw.Text);
container_child_number_element.* = vxfw.Text{
.text = container_child_number_text,
.style = .{
.bg = bg,
.fg = fg,
},
};
const widgets = try ctx.arena.alloc(vxfw.Widget, text_items.items.len);
for (text_items.items, 0..) |t, i| {
widgets[i] = t.widget();
const padding = try ctx.arena.create(vxfw.Padding);
padding.* = .{
.child = container_child_number_element.widget(),
.padding = vxfw.Padding.horizontal(1),
.fill = .{
.style = .{
.bg = bg,
.fg = fg,
},
},
};
try item_children_list.append(.{
.widget = try draw_context.fromWidget(padding.widget(), draw_context.maxHeight1, ctx.arena),
.flex = 0,
});
}
return widgets;
//FlexItems
const item_children = try ctx.arena.alloc(vxfw.FlexItem, item_children_list.items.len);
for (item_children_list.items, 0..) |item_child, i| {
item_children[i] = item_child;
}
// Item
const item_element = try ctx.arena.create(vxfw.FlexRow);
item_element.* = .{
.children = item_children,
};
try children.append(try draw_context.fromWidget(item_element.widget(), draw_context.maxHeight1, ctx.arena));
}
return .{ children.items, active_item_index };
}
};
@@ -463,3 +547,4 @@ const AppState = @import("../core/app_state.zig").AppState;
const handle_key = @import("./action_map.zig").handle_key;
const Arc = @import("zigrc").Arc;
const Observer = @import("../core/observable.zig").Observer;
const draw_context = @import("draw_context.zig");

View File

@@ -360,7 +360,7 @@ fn loadItems(
defer processed_number = location.children.data.items.len;
for (location.children.data.items[processed_number..]) |item_fullname| {
const resolvedItem = location.item.provider.getItemByFullName(item_fullname, &.{ .skipChildInit = true }, allocator) catch |e| {
const resolvedItem = location.item.provider.getItemByFullName(item_fullname, &.{}, allocator) catch |e| {
//TODO: save error to container errors
std.debug.print("error {} {s}\r\n", .{ e, item_fullname.path });
continue;