Compare commits

..

No commits in common. "main" and "zig" have entirely different histories.
main ... zig

15 changed files with 528 additions and 3566 deletions

1
.gitignore vendored
View file

@ -1 +0,0 @@
/target

66
3rd-party/cimgui/build.zig vendored Normal file
View file

@ -0,0 +1,66 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const dep_cimgui = b.dependency("cimgui", .{});
const dep_imgui = b.dependency("imgui", .{});
// create file tree for cimgui and imgui
const wf = b.addNamedWriteFiles("cimgui");
_ = wf.addCopyDirectory(dep_cimgui.path(""), "", .{});
_ = wf.addCopyDirectory(dep_imgui.path(""), "imgui", .{});
const root = wf.getDirectory();
// build cimgui as C/C++ library
const lib_cimgui = b.addStaticLibrary(.{
.name = "cimgui_clib",
.target = target,
.optimize = optimize,
.link_libc = true,
});
lib_cimgui.linkLibCpp();
lib_cimgui.addCSourceFiles(.{
.root = root,
.files = &.{
b.pathJoin(&.{"cimgui.cpp"}),
b.pathJoin(&.{ "imgui", "imgui.cpp" }),
b.pathJoin(&.{ "imgui", "imgui_widgets.cpp" }),
b.pathJoin(&.{ "imgui", "imgui_draw.cpp" }),
b.pathJoin(&.{ "imgui", "imgui_tables.cpp" }),
b.pathJoin(&.{ "imgui", "imgui_demo.cpp" }),
},
});
lib_cimgui.addIncludePath(root);
// make cimgui available as artifact, this then allows to inject
// the Emscripten include path in another build.zig
b.installArtifact(lib_cimgui);
// lib compilation depends on file tree
lib_cimgui.step.dependOn(&wf.step);
// translate-c the cimgui.h file
// NOTE: always run this with the host target, that way we don't need to inject
// the Emscripten SDK include path into the translate-C step when building for WASM
const cimgui_h = dep_cimgui.path("cimgui.h");
const translateC = b.addTranslateC(.{
.root_source_file = cimgui_h,
.target = b.host,
.optimize = optimize,
});
translateC.defineCMacroRaw("CIMGUI_DEFINE_ENUMS_AND_STRUCTS=\"\"");
const entrypoint = translateC.getOutput();
// build cimgui as a module with the header file as the entrypoint
const mod_cimgui = b.addModule("cimgui", .{
.root_source_file = entrypoint,
.target = target,
.optimize = optimize,
.link_libc = true,
.link_libcpp = true,
});
mod_cimgui.linkLibrary(lib_cimgui);
}

18
3rd-party/cimgui/build.zig.zon vendored Normal file
View file

@ -0,0 +1,18 @@
.{
.name = "cimgui",
.version = "0.0.0",
.dependencies = .{
.cimgui = .{
.url = "git+https://github.com/cimgui/cimgui.git#1.90.7",
.hash = "1220d925a8374fbc3a21a5b46025fb867672d85b4099d2151bed607618e08b0cd71c",
},
.imgui = .{
.url = "git+https://github.com/ocornut/imgui.git#v1.90.7",
.hash = "122072b125179c7cbdbbee8fa81d22a1050a950ad61cfeefee8dc0dca5423b5d05b9",
},
},
.paths = .{
"",
},
}

3338
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,10 +0,0 @@
[package]
name = "doggo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iced = {version = "0.12.1"}
rfd = { version = "0.14.1", default-features = false, features = ["gtk3"] }

110
build.zig Normal file
View file

@ -0,0 +1,110 @@
const std = @import("std");
const Build = std.Build;
const OptimizeMode = std.builtin.OptimizeMode;
const ResolvedTarget = Build.ResolvedTarget;
const Dependency = Build.Dependency;
const sokol = @import("sokol");
pub fn build(b: *Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// note that the sokol dependency is built with `.with_imgui_sokol = true`
const dep_sokol = b.dependency("sokol", .{
.target = target,
.optimize = optimize,
.with_sokol_imgui = true,
});
const dep_cimgui = b.dependency("cimgui", .{
.target = target,
.optimize = optimize,
});
// inject the cimgui header search path into the sokol C library compile step
const cimgui_root = dep_cimgui.namedWriteFiles("cimgui").getDirectory();
dep_sokol.artifact("sokol_clib").addIncludePath(cimgui_root);
// from here on different handling for native vs wasm builds
if (target.result.isWasm()) {
try buildWasm(b, target, optimize, dep_sokol, dep_cimgui);
} else {
try buildNative(b, target, optimize, dep_sokol, dep_cimgui);
}
try buildTui(b, target, optimize);
}
fn buildNative(b: *Build, target: ResolvedTarget, optimize: OptimizeMode, dep_sokol: *Dependency, dep_cimgui: *Dependency) !void {
const demo = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("src/gui/main.zig"),
.target = target,
.optimize = optimize,
});
demo.root_module.addImport("sokol", dep_sokol.module("sokol"));
demo.root_module.addImport("cimgui", dep_cimgui.module("cimgui"));
b.installArtifact(demo);
b.step("run-gui", "Run demo").dependOn(&b.addRunArtifact(demo).step);
}
fn buildWasm(b: *Build, target: ResolvedTarget, optimize: OptimizeMode, dep_sokol: *Dependency, dep_cimgui: *Dependency) !void {
// build the main file into a library, this is because the WASM 'exe'
// needs to be linked in a separate build step with the Emscripten linker
const demo = b.addStaticLibrary(.{
.name = "demo",
.root_source_file = b.path("src/gui/main.zig"),
.target = target,
.optimize = optimize,
});
demo.root_module.addImport("sokol", dep_sokol.module("sokol"));
demo.root_module.addImport("cimgui", dep_cimgui.module("cimgui"));
// get the Emscripten SDK dependency from the sokol dependency
const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
// need to inject the Emscripten system header include path into
// the cimgui C library otherwise the C/C++ code won't find
// C stdlib headers
const emsdk_incl_path = dep_emsdk.path("upstream/emscripten/cache/sysroot/include");
dep_cimgui.artifact("cimgui_clib").addSystemIncludePath(emsdk_incl_path);
// all C libraries need to depend on the sokol library, when building for
// WASM this makes sure that the Emscripten SDK has been setup before
// C compilation is attempted (since the sokol C library depends on the
// Emscripten SDK setup step)
dep_cimgui.artifact("cimgui_clib").step.dependOn(&dep_sokol.artifact("sokol_clib").step);
// create a build step which invokes the Emscripten linker
const link_step = try sokol.emLinkStep(b, .{
.lib_main = demo,
.target = target,
.optimize = optimize,
.emsdk = dep_emsdk,
.use_webgl2 = true,
.use_emmalloc = true,
.use_filesystem = false,
.shell_file_path = dep_sokol.path("src/sokol/web/shell.html").getPath(b),
});
// ...and a special run step to start the web build output via 'emrun'
const run = sokol.emRunStep(b, .{ .name = "demo", .emsdk = dep_emsdk });
run.step.dependOn(&link_step.step);
b.step("run-gui", "Run demo").dependOn(&run.step);
}
fn buildTui(b: *Build, target: ResolvedTarget, optimize: OptimizeMode) !void {
const tui = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("src/tui/main.zig"),
.target = target,
.optimize = optimize,
});
const vaxis_dep = b.dependency("vaxis", .{
.target = target,
.optimize = optimize,
});
tui.root_module.addImport("vaxis", vaxis_dep.module("vaxis"));
b.installArtifact(tui);
b.step("run-tui", "Run TUI").dependOn(&b.addRunArtifact(tui).step);
}

22
build.zig.zon Normal file
View file

@ -0,0 +1,22 @@
.{
.name = "doggo",
.version = "0.1.0",
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
.dependencies = .{
.sokol = .{
.url = "git+https://github.com/floooh/sokol-zig.git#d9f3ef983b7021417cc91bd5788cd925ca06aa83",
.hash = "12200a472b4f1a3a6db1002f39f10ae0cccf58ada45bf25ddc5fd48384dd187f55ea",
},
.cimgui = .{
.path = "3rd-party/cimgui",
},
.vaxis = .{
.url = "git+https://github.com/rockorager/libvaxis?ref=main#235e0bb27bc89655d8c66fc6378b31d4a98a117a",
.hash = "1220fef553676a4c90035db27c13ad609d5823198a94cc971e95ab9c680e3dcd2df0",
},
},
}

46
flake.lock generated
View file

@ -1,63 +1,23 @@
{ {
"nodes": { "nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1712211755,
"narHash": "sha256-KIJA4OvXFDXEeu7wstFDCxqZEfjaPQIowpzNww48TUw=",
"owner": "nix-community",
"repo": "fenix",
"rev": "39763c6e23a8423af316b85a74bad0cc5bc88d86",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1712163089, "lastModified": 1720058333,
"narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=", "narHash": "sha256-gM2RCi5XkxmcsZ44pUkKIYBiBMfZ6u7MdcZcykmccrs=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "fd281bd6b7d3e32ddfa399853946f782553163b5", "rev": "6842b061970bf96965d66fcc86a28e1f719aae95",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect" "type": "indirect"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"fenix": "fenix",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1712156296,
"narHash": "sha256-St7ZQrkrr5lmQX9wC1ZJAFxL8W7alswnyZk9d1se3Us=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "8e581ac348e223488622f4d3003cb2bd412bf27e",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -1,51 +1,30 @@
{ {
description = "Doggo flake"; description = "Doggo flake";
inputs = { outputs = { self, nixpkgs }:
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
nixpkgs.url = "nixpkgs/nixos-unstable";
};
outputs = { self, fenix, nixpkgs }:
let pkgs = nixpkgs.legacyPackages.x86_64-linux; let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in { in {
devShell.x86_64-linux = with pkgs; devShell.x86_64-linux = with pkgs;
mkShell rec { mkShell {
nativeBuildInputs = [ nativeBuildInputs = [
just just
pkg-config zig
protobuf zls
(fenix.packages.x86_64-linux.fromToolchainFile {
dir = ./.;
sha256 = "sha256-7QfkHty6hSrgNM0fspycYkRcB82eEqYa4CoAJ9qA3tU= ";
})
fenix.packages.x86_64-linux.rust-analyzer
]; ];
buildInputs = [ buildInputs = [
fontconfig lldb
vulkan-headers # GUI
vulkan-loader alsa-lib
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXext
xorg.libXrandr
xorg.libXinerama
libGL libGL
libGLU
libxkbcommon
wayland
# rfd
gtk3
]; ];
env = {
# WAYLAND_DISPLAY = ""; # Window has nor decoration on Wayland
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath buildInputs;
};
}; };
};
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt;
};
} }

Binary file not shown.

View file

@ -1,15 +1,9 @@
alias b := build alias b := build
build: build:
cargo build zig build
alias r := run gui:
run: zig build run-gui
nixVulkanIntel cargo run
fmt: tui:
nix fmt flake.nix zig build run-tui
# See https://github.com/zed-industries/zed/blob/main/docs/src/developing_zed__building_zed_linux.md
alias e := edit
edit:
WALYAND_DISPLAY= nixVulkanIntel zed .

View file

@ -1,3 +0,0 @@
[toolchain]
channel = "stable"
profile = "default"

92
src/gui/main.zig Normal file
View file

@ -0,0 +1,92 @@
const ig = @import("cimgui");
const sokol = @import("sokol");
const slog = sokol.log;
const sg = sokol.gfx;
const sapp = sokol.app;
const sglue = sokol.glue;
const simgui = sokol.imgui;
const state = struct {
var pass_action: sg.PassAction = .{};
};
export fn init() void {
// initialize sokol-gfx
sg.setup(.{
.environment = sglue.environment(),
.logger = .{ .func = slog.func },
});
// initialize sokol-imgui
simgui.setup(.{
.logger = .{ .func = slog.func },
});
// initial clear color
state.pass_action.colors[0] = .{
.load_action = .CLEAR,
.clear_value = .{ .r = 0.0, .g = 0.5, .b = 1.0, .a = 1.0 },
};
}
export fn frame() void {
// call simgui.newFrame() before any ImGui calls
simgui.newFrame(.{
.width = sapp.width(),
.height = sapp.height(),
.delta_time = sapp.frameDuration(),
.dpi_scale = sapp.dpiScale(),
});
//=== UI CODE STARTS HERE
var menu_bar_size: ig.ImVec2 = undefined;
if (ig.igBeginMainMenuBar()) {
ig.igGetWindowSize(&menu_bar_size);
defer ig.igEndMainMenuBar();
if (ig.igBeginMenu("File", true)) {
defer ig.igEndMenu();
if (ig.igMenuItem_Bool("Open", "o", false, true)) {
// Do something
}
}
}
const io = ig.igGetIO();
ig.igSetNextWindowPos(.{ .x = 0, .y = menu_bar_size.y }, ig.ImGuiCond_Always, .{ .x = 0, .y = 0 });
var content_size: ig.ImVec2 = io.*.DisplaySize;
content_size.y -= menu_bar_size.y;
ig.igSetNextWindowSize(content_size, ig.ImGuiCond_Always);
_ = ig.igBegin("Hello Dear ImGui!", 0, ig.ImGuiWindowFlags_NoMove | ig.ImGuiWindowFlags_NoResize | ig.ImGuiWindowFlags_NoTitleBar | ig.ImGuiWindowFlags_NoCollapse);
_ = ig.igColorEdit3("Background", &state.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None);
ig.igEnd();
//=== UI CODE ENDS HERE
// call simgui.render() inside a sokol-gfx pass
sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() });
simgui.render();
sg.endPass();
sg.commit();
}
export fn cleanup() void {
simgui.shutdown();
sg.shutdown();
}
export fn event(ev: [*c]const sapp.Event) void {
// forward input events to sokol-imgui
_ = simgui.handleEvent(ev.*);
}
pub fn main() void {
sapp.run(.{
.init_cb = init,
.frame_cb = frame,
.cleanup_cb = cleanup,
.event_cb = event,
.window_title = "Doggo",
.width = 800,
.height = 600,
.icon = .{ .sokol_default = true },
.logger = .{ .func = slog.func },
});
}

View file

@ -1,123 +0,0 @@
use iced::{
alignment,
widget::{button, column, container, row, scrollable, text, text_input, Column},
Element, Length, Padding, Sandbox, Settings,
};
use rfd::FileDialog;
#[derive(Debug, Clone)]
enum Message {
InputValue(String),
Submitted,
DeleteItem(usize),
}
/**
* This is your model. It contains all the data needed for your application to work properly.
* The model can only be updated with the `update` function.
*/
struct GroceryList {
grocery_items: Vec<String>,
input_value: String,
}
impl Sandbox for GroceryList {
type Message = Message;
/* Initialize your app */
fn new() -> GroceryList {
Self {
grocery_items: vec!["Eggs".to_owned(), "Milk".to_owned(), "Flour".to_owned()],
input_value: String::default(),
}
}
/**
* The title of the window. It will show up on the top of your application window.
*/
fn title(&self) -> String {
String::from("Grocery List App")
}
fn update(&mut self, message: Self::Message) {
match message {
Message::InputValue(value) => self.input_value = value,
Message::Submitted => {
let input_value = self.input_value.clone();
self.input_value = String::default(); // Clear the input value
let _file = FileDialog::new()
.set_directory("/home/ffreling/Sync")
.pick_file();
let file_str = _file.and_then(|p| Some(String::from(p.to_str().unwrap())));
println!("{:?}", file_str);
match file_str {
None => (),
Some(path_str) => self.grocery_items.push(path_str),
}
// self.grocery_items.push(input_value);
}
Message::DeleteItem(item) => {
let _files = FileDialog::new()
.set_directory("/home/ffreling/Sync")
.pick_file();
self.grocery_items.remove(item);
}
}
}
fn view(&self) -> Element<Self::Message> {
container(
column!(
items_list_view(&self.grocery_items),
row!(
text_input("Input grocery item", &self.input_value)
.on_input(|value| Message::InputValue(value))
.on_submit(Message::Submitted),
button("Submit").on_press(Message::Submitted)
)
.spacing(30)
.padding(Padding::from(30))
)
.align_items(iced::Alignment::Center),
)
.height(Length::Fill)
.width(Length::Fill)
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center)
.into()
}
fn theme(&self) -> iced::Theme {
iced::Theme::Dark
}
}
fn items_list_view(items: &Vec<String>) -> Element<'static, Message> {
let mut column = Column::new()
.spacing(20)
.align_items(iced::Alignment::Center)
.width(Length::Fill);
for (index, value) in items.into_iter().enumerate() {
column = column.push(grocery_item(index, value));
}
scrollable(container(column))
.height(250.0)
.width(300)
.into()
}
fn grocery_item(index: usize, value: &str) -> Element<'static, Message> {
row!(
text(value),
button("Delete").on_press(Message::DeleteItem(index))
)
.align_items(iced::Alignment::Center)
.spacing(30)
.into()
}
pub fn main() -> iced::Result {
GroceryList::run(Settings::default())
}

196
src/tui/main.zig Normal file
View file

@ -0,0 +1,196 @@
// const std = @import("std");
// pub fn main() !void {
// const stdout = std.io.getStdOut().writer();
// try stdout.print("Can spawn process: {}\n", .{std.process.can_spawn});
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
// const alloc = gpa.allocator();
// var lldb_child = std.process.Child.init(&[_][]const u8{"lldb-vscode"}, alloc);
// _ = try lldb_child.spawnAndWait();
// }
const std = @import("std");
const vaxis = @import("vaxis");
/// Set the default panic handler to the vaxis panic_handler. This will clean up the terminal if any
/// panics occur
pub const panic = vaxis.panic_handler;
/// Set some scope levels for the vaxis scopes
pub const std_options: std.Options = .{
.log_scope_levels = &.{
.{ .scope = .vaxis, .level = .warn },
.{ .scope = .vaxis_parser, .level = .warn },
},
};
/// Tagged union of all events our application will handle. These can be generated by Vaxis or your
/// own custom events
const Event = union(enum) {
key_press: vaxis.Key,
key_release: vaxis.Key,
mouse: vaxis.Mouse,
focus_in, // window has gained focus
focus_out, // window has lost focus
paste_start, // bracketed paste start
paste_end, // bracketed paste end
paste: []const u8, // osc 52 paste, caller must free
color_report: vaxis.Color.Report, // osc 4, 10, 11, 12 response
color_scheme: vaxis.Color.Scheme, // light / dark OS theme changes
winsize: vaxis.Winsize, // the window size has changed. This event is always sent when the loop
// is started
};
/// The application state
const MyApp = struct {
allocator: std.mem.Allocator,
// A flag for if we should quit
should_quit: bool,
/// The tty we are talking to
tty: vaxis.Tty,
/// The vaxis instance
vx: vaxis.Vaxis,
/// A mouse event that we will handle in the draw cycle
mouse: ?vaxis.Mouse,
pub fn init(allocator: std.mem.Allocator) !MyApp {
return .{
.allocator = allocator,
.should_quit = false,
.tty = try vaxis.Tty.init(),
.vx = try vaxis.init(allocator, .{}),
.mouse = null,
};
}
pub fn deinit(self: *MyApp) void {
// Deinit takes an optional allocator. You can choose to pass an allocator to clean up
// memory, or pass null if your application is shutting down and let the OS clean up the
// memory
self.vx.deinit(self.allocator, self.tty.anyWriter());
self.tty.deinit();
}
pub fn run(self: *MyApp) !void {
// Initialize our event loop. This particular loop requires intrusive init
var loop: vaxis.Loop(Event) = .{
.tty = &self.tty,
.vaxis = &self.vx,
};
try loop.init();
// Start the event loop. Events will now be queued
try loop.start();
try self.vx.enterAltScreen(self.tty.anyWriter());
// Query the terminal to detect advanced features, such as kitty keyboard protocol, etc.
// This will automatically enable the features in the screen you are in, so you will want to
// call it after entering the alt screen if you are a full screen application. The second
// arg is a timeout for the terminal to send responses. Typically the response will be very
// fast, however it could be slow on ssh connections.
try self.vx.queryTerminal(self.tty.anyWriter(), 1 * std.time.ns_per_s);
// Enable mouse events
try self.vx.setMouseMode(self.tty.anyWriter(), true);
// This is the main event loop. The basic structure is
// 1. Handle events
// 2. Draw application
// 3. Render
while (!self.should_quit) {
// pollEvent blocks until we have an event
loop.pollEvent();
// tryEvent returns events until the queue is empty
while (loop.tryEvent()) |event| {
try self.update(event);
}
// Draw our application after handling events
self.draw();
// It's best to use a buffered writer for the render method. TTY provides one, but you
// may use your own. The provided bufferedWriter has a buffer size of 4096
var buffered = self.tty.bufferedWriter();
// Render the application to the screen
try self.vx.render(buffered.writer().any());
try buffered.flush();
}
}
/// Update our application state from an event
pub fn update(self: *MyApp, event: Event) !void {
switch (event) {
.key_press => |key| {
// key.matches does some basic matching algorithms. Key matching can be complex in
// the presence of kitty keyboard encodings, this will generally be a good approach.
// There are other matching functions available for specific purposes, as well
if (key.matches('c', .{ .ctrl = true }))
self.should_quit = true;
},
.mouse => |mouse| self.mouse = mouse,
.winsize => |ws| try self.vx.resize(self.allocator, self.tty.anyWriter(), ws),
else => {},
}
}
/// Draw our current state
pub fn draw(self: *MyApp) void {
const msg = "Hello, world!";
// Window is a bounded area with a view to the screen. You cannot draw outside of a windows
// bounds. They are light structures, not intended to be stored.
const win = self.vx.window();
// Clearing the window has the effect of setting each cell to it's "default" state. Vaxis
// applications typically will be immediate mode, and you will redraw your entire
// application during the draw cycle.
win.clear();
// In addition to clearing our window, we want to clear the mouse shape state since we may
// be changing that as well
self.vx.setMouseShape(.default);
const child = win.child(.{
.x_off = (win.width / 2) - 7,
.y_off = win.height / 2 + 1,
.width = .{ .limit = msg.len },
.height = .{ .limit = 1 },
});
// mouse events are much easier to handle in the draw cycle. Windows have a helper method to
// determine if the event occurred in the target window. This method returns null if there
// is no mouse event, or if it occurred outside of the window
const style: vaxis.Style = if (child.hasMouse(self.mouse)) |_| blk: {
// We handled the mouse event, so set it to null
self.mouse = null;
self.vx.setMouseShape(.pointer);
break :blk .{ .reverse = true };
} else .{};
// Print a text segment to the screen. This is a helper function which iterates over the
// text field for graphemes. Alternatively, you can implement your own print functions and
// use the writeCell API.
_ = try child.printSegment(.{ .text = msg, .style = style }, .{});
}
};
/// Keep our main function small. Typically handling arg parsing and initialization only
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const deinit_status = gpa.deinit();
//fail test; can't try in defer as defer is executed after we return
if (deinit_status == .leak) {
std.log.err("memory leak", .{});
}
}
const allocator = gpa.allocator();
// Initialize our application
var app = try MyApp.init(allocator);
defer app.deinit();
// Run the application
try app.run();
}