From 53b93bad6489a64f5da84c82397b8d0260951d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Wed, 28 May 2025 18:07:25 +0200 Subject: [PATCH] feat: selected's children --- src/console/main.zig | 155 ++++++++++++++++++++++++++++++---------- src/core/app_state.zig | 38 +++++++--- src/core/observable.zig | 5 ++ src/core/tab/tab.zig | 83 +++++++++++++++++++-- 4 files changed, 229 insertions(+), 52 deletions(-) diff --git a/src/console/main.zig b/src/console/main.zig index 0cece21..b453df4 100644 --- a/src/console/main.zig +++ b/src/console/main.zig @@ -2,7 +2,8 @@ const Model = struct { crash: bool = false, allocator: std.mem.Allocator, current_items_view: *vxfw.ListView, - current_items_view_widget: vxfw.Widget, + parent_items_view: *vxfw.ListView, + current_children_view: *vxfw.ListView, app_common_model: *AppCommonModel, root_provider: *RootProvider, @@ -19,7 +20,7 @@ const Model = struct { fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { const vm: *Model = @ptrCast(@alignCast(ptr)); switch (event) { - .init => return ctx.requestFocus(vm.current_items_view_widget), + .init => return ctx.requestFocus(vm.current_items_view.widget()), .key_press => |key| { if (key.matches('c', .{ .ctrl = true })) { ctx.quit = true; @@ -28,10 +29,10 @@ const Model = struct { if (key.matches('r', .{})) { // vm.crash = true; ctx.redraw = true; - return ctx.requestFocus(vm.current_items_view_widget); + return ctx.requestFocus(vm.current_items_view.widget()); } }, - .focus_in => return ctx.requestFocus(vm.current_items_view_widget), + .focus_in => return ctx.requestFocus(vm.current_items_view.widget()), else => {}, } } @@ -61,6 +62,8 @@ const Model = struct { const max_size = ctx.max.size(); try createCurrentItems(ctx, vm); + try createParentItems(ctx, vm); + try createChildrenItems(ctx, vm); { if (vm.current_items_view.children.slice.len == 0) { @@ -85,18 +88,46 @@ const Model = struct { const parentItemsWidth = max_size.width / 9; const childrenWidth = max_size.width * 4 / 9; const currentItemsWidth = max_size.width - parentItemsWidth - childrenWidth; - const currentItemsTop = 1; - const list_surface: vxfw.SubSurface = .{ - .origin = .{ .row = currentItemsTop, .col = parentItemsWidth }, - .surface = try vm.current_items_view_widget - .draw(ctx.withConstraints(ctx.min, .{ - .width = currentItemsWidth, - .height = ctx.max.height.? - currentItemsTop, - })), - // .surface = try vm.current_items.widget().draw(ctx), - }; - try rootWidgets.append(list_surface); + + { + const parent_items_list_surface: vxfw.SubSurface = .{ + .origin = .{ .row = currentItemsTop, .col = 0 }, + .surface = try vm.parent_items_view + .widget() + .draw(ctx.withConstraints(ctx.min, .{ + .width = parentItemsWidth, + .height = ctx.max.height.? - currentItemsTop, + })), + }; + try rootWidgets.append(parent_items_list_surface); + } + + { + const current_items_list_surface: vxfw.SubSurface = .{ + .origin = .{ .row = currentItemsTop, .col = parentItemsWidth + 1 }, + .surface = try vm.current_items_view + .widget() + .draw(ctx.withConstraints(ctx.min, .{ + .width = currentItemsWidth - 1, + .height = ctx.max.height.? - currentItemsTop, + })), + }; + try rootWidgets.append(current_items_list_surface); + } + + { + const current_children_list_surface: vxfw.SubSurface = .{ + .origin = .{ .row = currentItemsTop, .col = parentItemsWidth + currentItemsWidth + 1 }, + .surface = try vm.current_children_view + .widget() + .draw(ctx.withConstraints(ctx.min, .{ + .width = childrenWidth - 1, + .height = ctx.max.height.? - currentItemsTop, + })), + }; + try rootWidgets.append(current_children_list_surface); + } const header = try ctx.arena.create(std.ArrayList(vxfw.Widget)); header.* = std.ArrayList(vxfw.Widget).init(ctx.arena); @@ -182,20 +213,49 @@ const Model = struct { } fn createCurrentItems(ctx: vxfw.DrawContext, vm: *Model) !void { - if (vm.crash) @panic("asd123"); + const currentTab = vm.app_common_model.appState.currentTab; + const current_full_name = if (currentTab.currentItem) |currentItem| blk: { + const arc_copy = currentItem.retain(); + defer if (arc_copy.releaseUnwrap()) |item| item.deinit(); + break :blk models.FullName{ .path = try ctx.arena.dupe(u8, currentItem.value.*.fullName.path) }; + } else null; + + const widgets = try createItems(ctx, ¤tTab.currentItems, current_full_name); + if (widgets) |w| { + vm.current_items_view.children.slice = w; + } else { + vm.current_items_view.children.slice = &.{}; + } + } + + fn createParentItems(ctx: vxfw.DrawContext, vm: *Model) !void { + const currentTab = vm.app_common_model.appState.currentTab; + //TODO: current fullname + const widgets = try createItems(ctx, ¤tTab.currentParentItems, null); + if (widgets) |w| { + vm.parent_items_view.children.slice = w; + } + } + + fn createChildrenItems(ctx: vxfw.DrawContext, vm: *Model) !void { + const currentTab = vm.app_common_model.appState.currentTab; + //TODO: current fullname + const widgets = try createItems(ctx, ¤tTab.currentChildren, null); + if (widgets) |w| { + vm.current_children_view.children.slice = w; + } + } + fn createItems( + ctx: vxfw.DrawContext, + items1: *locked(?std.ArrayList(Arc(*models.Item))), + current_full_name: ?models.FullName, + ) !?[]vxfw.Widget { const text_items = blk2: { - const currentTab = vm.app_common_model.appState.currentTab; - const current_full_name = if (currentTab.currentItem) |currentItem| blk: { - const arc_copy = currentItem.retain(); - defer if (arc_copy.releaseUnwrap()) |item| item.deinit(); - break :blk models.FullName{ .path = try ctx.arena.dupe(u8, currentItem.value.*.fullName.path) }; - } else null; + items1.mutex.lock(); + defer items1.mutex.unlock(); - currentTab.currentItems.mutex.lock(); - defer currentTab.currentItems.mutex.unlock(); - - break :blk2 if (currentTab.currentItems.data) |items| blk: { + break :blk2 if (items1.data) |items| blk: { const children = try ctx.arena.create(std.ArrayList(*vxfw.Text)); children.* = std.ArrayList(*vxfw.Text).init(ctx.arena); @@ -231,7 +291,8 @@ const Model = struct { break :colors .{ fg, bg }; }; - const text = try ctx.arena.dupe(u8, child.displayName); + //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, @@ -243,13 +304,11 @@ const Model = struct { }, }; - // children[i] = text_element; try children.append(text_element); } break :blk children; } else { - vm.current_items_view.children.slice = &.{}; - return; + return null; }; }; @@ -258,7 +317,7 @@ const Model = struct { widgets[i] = t.widget(); } - vm.current_items_view.children.slice = widgets; + return widgets; } }; @@ -313,14 +372,20 @@ pub fn main() !void { defer allocator.free(items); items[0] = empty_text_element.widget(); - var list: vxfw.ListView = .{ + var current_items_list: vxfw.ListView = .{ + .draw_cursor = false, + .children = .{ .slice = items }, + }; + + var parent_items_list: vxfw.ListView = .{ + .draw_cursor = false, + .children = .{ .slice = items }, + }; + + var current_children_view: vxfw.ListView = .{ .draw_cursor = false, .children = .{ .slice = items }, }; - var list_widget = list.widget(); - list_widget.eventHandler = struct { - fn a(_: *anyopaque, _: *vxfw.EventContext, _: vxfw.Event) anyerror!void {} - }.a; var appState: AppState = AppState.init(allocator); try appState.addTab(tab1); @@ -333,11 +398,13 @@ pub fn main() !void { try AppCommonModel.init(&app_common_model, allocator, appState); defer app_common_model.deinit(); + model.* = .{ .allocator = allocator, .app_common_model = &app_common_model, - .current_items_view = &list, - .current_items_view_widget = list_widget, + .current_items_view = ¤t_items_list, + .parent_items_view = &parent_items_list, + .current_children_view = ¤t_children_view, .root_provider = &rootProvider, }; @@ -346,6 +413,16 @@ pub fn main() !void { var app = try vxfw.App.init(allocator); defer app.deinit(); + const asd = Observer(*Tab){ + .ctx = @ptrCast(@alignCast(&app)), + .update = (struct{fn update(ctx: *anyopaque, _: *Tab)void { + const app1: *vxfw.App = @ptrCast(@alignCast(ctx)); + _ = app1; + }}).update, + }; + + try app_common_model.appState.tabChildrenLoaded.attach(&asd); + try app.run(model.widget(), .{}); model.app_common_model.usage_number.data -= 1; @@ -370,3 +447,5 @@ const locked = @import("../core/sync.zig").locked; const AppCommonModel = @import("../app_common/Model.zig"); 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; diff --git a/src/core/app_state.zig b/src/core/app_state.zig index d8ad66e..1e250cf 100644 --- a/src/core/app_state.zig +++ b/src/core/app_state.zig @@ -1,25 +1,30 @@ pub const UnknownTabError = error.UnknownTab; pub const AppState = struct { + allocator: std.mem.Allocator, currentTab: *Tab = undefined, tabs: std.ArrayList(*Tab), currentTabChanged: Observable(*Tab), - tabPreCurrentItemsUnload: Observable(*Tab), + tabChildrenLoaded: Observable(*Tab), pub fn init(allocator: std.mem.Allocator) AppState { return .{ + .allocator = allocator, .tabs = std.ArrayList(*Tab).init(allocator), .currentTabChanged = Observable(*Tab).init(allocator), - .tabPreCurrentItemsUnload = Observable(*Tab).init(allocator), + .tabChildrenLoaded = Observable(*Tab).init(allocator), }; } pub fn addTab(self: *AppState, tab: *Tab) !void { try self.tabs.append(tab); - // tab.preCurrentItemsUnload.attach(.{ - // .ctx = @ptrCast(@alignCast(self)), - // .update = preCurrentItemsUnload, - // }); + const tabChildrenLoadedHandlerObserver = try tab.childrenLoaded.allocator.create(Observer(*Tab)); + tabChildrenLoadedHandlerObserver.* = Observer(*Tab){ + .ctx = @ptrCast(@alignCast(self)), + .update = tabChildrenLoadedHandler, + }; + + try tab.childrenLoaded.attach(tabChildrenLoadedHandlerObserver); } pub fn setCurrentTab(self: *AppState, newTab: *Tab) !void { @@ -34,8 +39,20 @@ pub const AppState = struct { self.currentTabChanged.notify(newTab); } + pub fn removeTab(self: *AppState, tab: *Tab) void { + const index = for (tab.childrenLoaded.observers, 0..) |observer, i| { + if (observer.ctx == self) break i; + } else null; + + if (index) |i| { + tab.childrenLoaded.observers.swapRemove(i); + } + + //TODO: remove from tabs + } + pub fn deinit(self: *AppState) void { - self.tabPreCurrentItemsUnload.deinit(); + self.tabChildrenLoaded.deinit(); self.currentTabChanged.deinit(); for (self.tabs.items) |tab| { tab.deinit(); @@ -43,12 +60,13 @@ pub const AppState = struct { self.tabs.deinit(); } - fn preCurrentItemsUnload(ctx: *anyopaque, tab: *Tab) void { - const self: AppState = @ptrCast(@alignCast(ctx)); - self.tabPreCurrentItemsUnload.notify(tab); + fn tabChildrenLoadedHandler(ctx: *anyopaque, tab: *Tab) void { + const self: *AppState = @ptrCast(@alignCast(ctx)); + self.tabChildrenLoaded.notify(tab); } }; const std = @import("std"); const Tab = @import("tab/tab.zig").Tab; const Observable = @import("observable.zig").Observable; +const Observer = @import("observable.zig").Observer; diff --git a/src/core/observable.zig b/src/core/observable.zig index 04c19ff..4bd629f 100644 --- a/src/core/observable.zig +++ b/src/core/observable.zig @@ -1,15 +1,20 @@ pub fn Observable(T: type) type { return struct { + allocator: std.mem.Allocator, observers: std.ArrayList(*const Observer(T)), const Self = @This(); pub fn init(allocator: std.mem.Allocator) Self { return .{ + .allocator = allocator, .observers = std.ArrayList(*const Observer(T)).init(allocator), }; } pub fn deinit(self: *Self) void { + for(self.observers)|o| { + self.allocator.destroy(o); + } self.observers.deinit(); } diff --git a/src/core/tab/tab.zig b/src/core/tab/tab.zig index 0821ef7..f01b17b 100644 --- a/src/core/tab/tab.zig +++ b/src/core/tab/tab.zig @@ -3,6 +3,8 @@ pub const Tab = struct { currentLocation: ?*Container, currentItems: locked(?std.ArrayList(Arc(*Item))), currentParentItems: locked(?std.ArrayList(Arc(*Item))), + currentChildren: locked(?std.ArrayList(Arc(*Item))), + childrenLoaded: Observable(*Tab), currentLocationChanged: Observable(?*Container), currentItemFullName: ?models.FullName, currentItem: ?Arc(*models.Item), @@ -21,7 +23,9 @@ pub const Tab = struct { .allocator = allocator, .currentItems = .{ .data = null }, .currentParentItems = .{ .data = null }, + .currentChildren = .{ .data = null }, .currentLocationChanged = Observable(?*Container).init(allocator), + .childrenLoaded = Observable(*Tab).init(allocator), .currentLocation = null, .currentRequested = null, .currentItem = null, @@ -169,6 +173,13 @@ pub const Tab = struct { self.currentItemFullName = models.FullName{ .path = try self.allocator.dupe(u8, arc.value.*.fullName.path), }; + + switch (arc.value.*.item) { + .container => { + std.Thread.Pool.spawn(self.threadPool, loadChildrenWrapper, .{ self, arc.retain() }) catch unreachable; + }, + else => {}, + } } fn loadItemsWrapper(self: *Tab, location: *Container) void { @@ -180,6 +191,7 @@ pub const Tab = struct { fn loadParentItemsWrapper(self: *Tab, location: *Container) void { const parentAbsolutePath = location.item.parent orelse return; + var arena = std.heap.ArenaAllocator.init(self.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -188,7 +200,7 @@ pub const Tab = struct { .path = allocator.dupe(u8, parentAbsolutePath.path.path) catch return, }; - const resolvedParent = location.item.provider.getItemByFullName(parentFullName, &.{ .skipChildInit = true }, allocator) catch |e| { + const resolvedParent = location.item.provider.getItemByFullName(parentFullName, &.{}, allocator) catch |e| { std.debug.print("error {} {s}\r\n", .{ e, parentFullName.path }); return; }; @@ -196,9 +208,47 @@ pub const Tab = struct { .container => |c| c, else => return, }; - loadItems(*Tab, self, parentContainer, &self.currentParentItems, (struct { - fn a(_: *Tab) void {} - }).a, self.allocator) catch {}; + loadItems( + void, + {}, + parentContainer, + &self.currentParentItems, + (struct { + fn a(_: void) void {} + }).a, + self.allocator, + ) catch {}; + } + + fn loadChildrenWrapper(self: *Tab, newSelectedItemArc: Arc(*models.Item)) void { + defer if (newSelectedItemArc.releaseUnwrap()) |item| item.deinit(); + + var arena = std.heap.ArenaAllocator.init(self.allocator); + defer arena.deinit(); + + const allocator = arena.allocator(); + + const resolvedChild = newSelectedItemArc.value.*.provider.getItemByFullName(newSelectedItemArc.value.*.fullName, &.{}, allocator) catch |e| { + std.debug.print("error {} {s}\r\n", .{ e, newSelectedItemArc.value.*.fullName.path }); + return; + }; + const childContainer = switch (resolvedChild.item) { + .container => |c| c, + else => return, + }; + + loadItems( + void, + {}, + childContainer, + &self.currentChildren, + (struct { + fn a(_: void) void {} + }).a, + self.allocator, + ) catch {}; + + self.childrenLoaded.notify(self); } pub fn deinit(self: *Tab) void { @@ -217,10 +267,35 @@ pub const Tab = struct { currentItems.deinit(); } + const data2 = self.currentParentItems.data; + self.currentParentItems.data = null; + if (data2) |currentItems| { + for (currentItems.items) |arc_item| { + if (arc_item.releaseUnwrap()) |item| { + item.deinit(); + } + } + currentItems.deinit(); + } + + const data3 = self.currentChildren.data; + self.currentChildren.data = null; + if (data3) |currentItems| { + for (currentItems.items) |arc_item| { + if (arc_item.releaseUnwrap()) |item| { + item.deinit(); + } + } + currentItems.deinit(); + } + self.cleanCurrentItem(); self.cleanCurrentRequested(); + self.currentLocationChanged.deinit(); + self.childrenLoaded.deinit(); + self.allocator.destroy(self); }