const std = @import("std"); const dvui = @import("dvui"); const entypo = dvui.entypo; const Backend = @import("SDLBackend"); var gpa_instance = std.heap.GeneralPurposeAllocator(.{}){}; const gpa = gpa_instance.allocator(); const vsync = true; pub fn main() !void { var backend = try Backend.init(.{ .width = 480, .height = 360, .vsync = vsync, .title = "File Picker", }); defer backend.deinit(); var win = try dvui.Window.init(@src(), 0, gpa, backend.backend()); defer win.deinit(); win.theme = &dvui.Adwaita.light; var ctx = try Ctx.init(); defer ctx.deinit(); loop: while (true) { var nstime = win.beginWait(backend.hasEvent()); try win.begin(nstime); backend.clear(); const quit = try backend.addAllEvents(&win); if (quit or .exit == try ctx.frame()) break :loop; const end_micros = try win.end(.{}); backend.setCursor(win.cursorRequested()); backend.renderPresent(); const wait_event_micros = win.waitTime(end_micros, null); backend.waitEventTimeout(wait_event_micros); } } pub const Ctx = struct { path: []u8, filename: []u8, entries: std.ArrayListUnmanaged(std.fs.IterableDir.Entry) = .{}, pub fn init() !Ctx { var ret = Ctx{ .path = try gpa.alloc(u8, std.fs.MAX_PATH_BYTES), .filename = try gpa.alloc(u8, std.fs.MAX_PATH_BYTES), }; ret.path.len = 0; ret.filename.len = 0; try ret.list_files(); return ret; } pub fn deinit(ctx: *Ctx) void { for (ctx.entries.items) |entry| { gpa.free(entry.name); } ctx.entries.deinit(gpa); @setRuntimeSafety(false); gpa.free(ctx.filename[0..std.fs.MAX_PATH_BYTES]); gpa.free(ctx.path[0..std.fs.MAX_PATH_BYTES]); } pub const ExitStatus = enum { ok, exit }; pub fn frame(ctx: *Ctx) !ExitStatus { const bg = dvui.Options{ .background = true, .expand = .horizontal, .corner_radius = dvui.Rect.all(0), }; const opts = dvui.Options{ .corner_radius = dvui.Rect.all(0), }; var box = try dvui.box(@src(), .vertical, bg.override(.{ .expand = .both, })); defer box.deinit(); { var top = try dvui.box(@src(), .horizontal, bg); defer top.deinit(); if (try dvui.button(@src(), "..", opts)) { try std.os.chdir(".."); try ctx.list_files(); } const te = try dvui.textEntry(@src(), .{ .text = ctx.path }, bg); defer te.deinit(); } { var bottom = try dvui.box(@src(), .horizontal, opts.override(.{ .expand = .horizontal, .gravity_y = 1, })); defer bottom.deinit(); const button_enabled = ctx.filename.len != 0; var button_opts = opts.override(.{ .gravity_x = 1 }); if (button_enabled) { button_opts.color_style = .accent; } else { button_opts.color_style = .control; button_opts.color_hover = button_opts.color(.fill); } if (try dvui.button(@src(), "ok", button_opts) and button_enabled) { std.debug.print("{s}/{s}\n", .{ ctx.path, ctx.filename }); return .exit; } const tl = try dvui.textEntry(@src(), .{ .text = ctx.filename }, opts.override(.{ .expand = .horizontal, })); defer tl.deinit(); } { var inner = try dvui.box(@src(), .vertical, .{ .expand = .both, .margin = dvui.Rect.all(12), }); defer inner.deinit(); var scroll = try dvui.scrollArea(@src(), .{}, .{ .expand = .both }); defer scroll.deinit(); entries: for (ctx.entries.items, 0..) |entry, i| { const name, const icon, const color_style: dvui.Theme.ColorStyle = switch (entry.kind) { .directory => .{ "folder", entypo.folder, .control }, .file => .{ "file", entypo.text_document, .window }, else => .{ "other", entypo.help, .content }, }; const id = opts.override(.{ .id_extra = i }); const wide = id.override(.{ .color_style = color_style, .expand = .horizontal, .margin = dvui.Rect.all(0), }); var bw = dvui.ButtonWidget.init(@src(), .{}, wide); try bw.install(); bw.processEvents(); try bw.drawBackground(); const m = try dvui.menu(@src(), .horizontal, id); try dvui.icon(@src(), name, icon, id.override(.{ .gravity_y = 0.4 })); try dvui.labelNoFmt(@src(), entry.name, id); m.deinit(); var clicked = bw.clicked(); try bw.drawFocus(); bw.deinit(); if (clicked) switch (entry.kind) { .directory => { try std.os.chdir(entry.name); try ctx.list_files(); break :entries; }, else => { ctx.filename.len = entry.name.len; std.mem.copy(u8, ctx.filename, entry.name); }, }; } } return .ok; } pub fn list_files(ctx: *Ctx) !void { const path = @as([*]u8, @ptrCast(ctx.path))[0..std.fs.MAX_PATH_BYTES]; ctx.path = try std.os.realpath(".", path); const cwd = std.fs.cwd(); var iterable = try cwd.openIterableDir(".", .{}); defer iterable.close(); var iterator = iterable.iterate(); for (ctx.entries.items) |entry| { gpa.free(entry.name); } ctx.entries.clearRetainingCapacity(); while (try iterator.next()) |iter_entry| { const entry = try ctx.entries.addOne(gpa); entry.name = try gpa.dupe(u8, iter_entry.name); entry.kind = iter_entry.kind; } const Entry = std.fs.IterableDir.Entry; const items = ctx.entries.items; const sort = struct { pub fn less_than_name(_: void, l: Entry, r: Entry) bool { return std.mem.lessThan(u8, l.name, r.name); } pub fn less_than_kind(_: void, l: Entry, r: Entry) bool { return @intFromEnum(l.kind) < @intFromEnum(r.kind); } }; std.mem.sort(Entry, items, {}, sort.less_than_name); std.mem.sort(Entry, items, {}, sort.less_than_kind); } };