summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE28
-rw-r--r--build.zig43
-rw-r--r--build.zig.zon11
-rw-r--r--flake.lock129
-rw-r--r--flake.nix27
-rw-r--r--pick.zig184
7 files changed, 424 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e73c965
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+zig-cache/
+zig-out/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..82741f9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2023, Kitty-Cricket Piapiac
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..f9bae63
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,43 @@
+const std = @import("std");
+const dvui = @import("dvui");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const dvui_dep = b.dependency("dvui", .{
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "pick",
+ .root_source_file = .{ .path = "pick.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ exe.addModule("dvui", dvui_dep.module("dvui"));
+ exe.addModule("SDLBackend", dvui_dep.module("SDLBackend"));
+
+ dvui.link_deps(exe, dvui_dep.builder);
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+ const unit_tests = b.addTest(.{
+ .root_source_file = .{ .path = "pick.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const run_unit_tests = b.addRunArtifact(unit_tests);
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_unit_tests.step);
+}
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644
index 0000000..f9365d0
--- /dev/null
+++ b/build.zig.zon
@@ -0,0 +1,11 @@
+.{
+ .name = "pick",
+ .version = "0.0.1",
+ .paths = .{ "." },
+ .dependencies = .{
+ .dvui = .{
+ .url = "https://git.disroot.org/kcp/dvui/archive/cb349fd04e983aded2b552ad0c746c32dd22778e.tar.gz",
+ .hash = "12206ea27c2bd175d16911e569c8d71f1a1005d3c5aef4feb841b08d404ab4176c91",
+ },
+ },
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..f386b09
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,129 @@
+{
+ "nodes": {
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1694529238,
+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_2": {
+ "locked": {
+ "lastModified": 1659877975,
+ "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1699099776,
+ "narHash": "sha256-X09iKJ27mGsGambGfkKzqvw5esP1L/Rf8H3u3fCqIiU=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "85f1ba3e51676fa8cc604a3d863d729026a6b8eb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1689088367,
+ "narHash": "sha256-Y2tl2TlKCWEHrOeM9ivjCLlRAKH3qoPUE/emhZECU14=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "5c9ddb86679c400d6b7360797b8a22167c2053f8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "release-23.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs",
+ "zig": "zig"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "zig": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "flake-utils": "flake-utils_2",
+ "nixpkgs": "nixpkgs_2"
+ },
+ "locked": {
+ "lastModified": 1699790839,
+ "narHash": "sha256-ooq3+JFM7dUop2td9B0HvHouEIp1llJkjayumEQhbAI=",
+ "owner": "mitchellh",
+ "repo": "zig-overlay",
+ "rev": "0d94a374db81dbdd9b0039c9378b2ece9d2a12d4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "mitchellh",
+ "repo": "zig-overlay",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..162247d
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,27 @@
+{
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ zig.url = "github:mitchellh/zig-overlay";
+ };
+ outputs = { self, nixpkgs, flake-utils, zig }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = import nixpkgs {
+ inherit system;
+ overlays = [
+ zig.overlays.default
+ ];
+ };
+ in {
+ devShells.default = pkgs.mkShell {
+ buildInputs = [
+ pkgs.SDL2
+ pkgs.SDL2.dev
+ pkgs.pkg-config
+ pkgs.zigpkgs.master
+ ];
+ };
+ }
+ );
+}
diff --git a/pick.zig b/pick.zig
new file mode 100644
index 0000000..76b1213
--- /dev/null
+++ b/pick.zig
@@ -0,0 +1,184 @@
+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();
+
+ 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{};
+ try ret.list_files();
+ return ret;
+ }
+
+ 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 scroll = try dvui.scrollArea(@src(), .{}, .{ .expand = .both });
+ defer scroll.deinit();
+
+ for (ctx.entries.items, 0..) |entry, i| {
+ const name, const icon, const color_style: dvui.Theme.ColorStyle = switch (entry.kind) {
+ .directory => .{ "folder", entypo.folder, .window },
+ .file => .{ "file", entypo.text_document, .control },
+ 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();
+ },
+ else => {
+ gpa.free(ctx.filename);
+ ctx.filename = try gpa.dupe(u8, entry.name);
+ },
+ };
+ }
+ }
+
+ return .ok;
+ }
+
+ pub fn list_files(ctx: *Ctx) !void {
+ var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ const path = try std.os.realpath(".", &buf);
+ ctx.path = try gpa.dupe(u8, path);
+
+ const cwd = std.fs.cwd();
+ const iterable = try cwd.openIterableDir(".", .{});
+ var iterator = iterable.iterate();
+
+ ctx.entries.clearRetainingCapacity();
+
+ while (try iterator.next()) |entry| {
+ const name = try gpa.dupe(u8, entry.name);
+ try ctx.entries.append(gpa, .{ .name = name, .kind = entry.kind });
+ }
+
+ const Entry = std.fs.IterableDir.Entry;
+ const items = ctx.entries.items;
+ const closures = struct {
+ pub fn less_than_0(_: void, l: Entry, r: Entry) bool {
+ return std.mem.lessThan(u8, l.name, r.name);
+ }
+ pub fn less_than_1(_: void, l: Entry, r: Entry) bool {
+ return @intFromEnum(l.kind) < @intFromEnum(r.kind);
+ }
+ };
+ std.mem.sort(Entry, items, {}, closures.less_than_0);
+ std.mem.sort(Entry, items, {}, closures.less_than_1);
+ }
+};