Compare commits
3 Commits
4db4b9fa81
...
7301c68b7e
| Author | SHA1 | Date | |
|---|---|---|---|
| 7301c68b7e | |||
| f563ff931b | |||
| 6eea7329c8 |
22
README.md
22
README.md
@ -4,6 +4,28 @@
|
|||||||
zig build run
|
zig build run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
* https://www.ni.com/docs/en-US/bundle/ni-daqmx-c-api-ref/page/cdaqmx/help_file_title.html
|
||||||
|
* https://www.ni.com/en/support/documentation/supplemental/06/getting-started-with-ni-daqmx--main-page.html
|
||||||
|
* https://knowledge.ni.com/KnowledgeArticleDetails?id=kA03q000000YGfDCAW&l=en-LT
|
||||||
|
* https://ziglang.org/learn/build-system/
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* Use downsampling for faster rendering of samples. When viewing many samples use dowsampled versions of data for rendering. Because you either way, you won't be able to see the detail.
|
* Use downsampling for faster rendering of samples. When viewing many samples use dowsampled versions of data for rendering. Because you either way, you won't be able to see the detail.
|
||||||
|
|
||||||
|
* Export .xcf files at build time
|
||||||
|
```
|
||||||
|
(let* (
|
||||||
|
(image (car (gimp-file-load RUN-NONINTERACTIVE "./icon.xcf" "./icon.xcf")))
|
||||||
|
(merged-layer (car (gimp-image-merge-visible-layers image CLIP-TO-BOTTOM-LAYER)))
|
||||||
|
)
|
||||||
|
(file-png-save RUN-NONINTERACTIVE image merged-layer "./icon.png" "./icon.png" 0 9 0 0 0 0 0)
|
||||||
|
(gimp-image-delete image)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
gimp-console-2.10.exe -i -b <batch> -b "(gimp-quit 0)"
|
||||||
|
```
|
||||||
114
build.zig
114
build.zig
@ -1,15 +1,105 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Module = std.Build.Module;
|
const Module = std.Build.Module;
|
||||||
|
|
||||||
|
const AddExecutableIcon = struct {
|
||||||
|
obj: *std.Build.Step.Compile,
|
||||||
|
step: std.Build.Step,
|
||||||
|
icon_file: std.Build.LazyPath,
|
||||||
|
resource_file: std.Build.LazyPath,
|
||||||
|
|
||||||
|
fn init(obj: *std.Build.Step.Compile, icon_file: std.Build.LazyPath) *AddExecutableIcon {
|
||||||
|
const b = obj.step.owner;
|
||||||
|
const self = b.allocator.create(AddExecutableIcon) catch @panic("OOM");
|
||||||
|
|
||||||
|
self.obj = obj;
|
||||||
|
self.step = std.Build.Step.init(.{
|
||||||
|
.id = .custom,
|
||||||
|
.name = "add executable icon",
|
||||||
|
.owner = b,
|
||||||
|
.makeFn = make
|
||||||
|
});
|
||||||
|
|
||||||
|
self.icon_file = icon_file;
|
||||||
|
icon_file.addStepDependencies(&self.step);
|
||||||
|
|
||||||
|
const write_files = b.addWriteFiles();
|
||||||
|
self.resource_file = write_files.add("resource-file.rc", "");
|
||||||
|
self.step.dependOn(&write_files.step);
|
||||||
|
|
||||||
|
self.obj.addWin32ResourceFile(.{
|
||||||
|
.file = self.resource_file,
|
||||||
|
});
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make(step: *std.Build.Step, _: std.Progress.Node) !void {
|
||||||
|
const b = step.owner;
|
||||||
|
const self: *AddExecutableIcon = @fieldParentPtr("step", step);
|
||||||
|
|
||||||
|
const resource_file = try std.fs.createFileAbsolute(self.resource_file.getPath(b), .{ });
|
||||||
|
defer resource_file.close();
|
||||||
|
|
||||||
|
const relative_icon_path = try std.fs.path.relative(
|
||||||
|
b.allocator,
|
||||||
|
self.resource_file.dirname().getPath(b),
|
||||||
|
self.icon_file.getPath(b)
|
||||||
|
);
|
||||||
|
std.mem.replaceScalar(u8, relative_icon_path, '\\', '/');
|
||||||
|
|
||||||
|
try resource_file.writeAll("IDI_ICON ICON \"");
|
||||||
|
try resource_file.writeAll(relative_icon_path);
|
||||||
|
try resource_file.writeAll("\"");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn buildStbImage(b: *std.Build) *std.Build.Module {
|
||||||
|
const module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("libs/stb_image/root.zig"),
|
||||||
|
.link_libc = true
|
||||||
|
});
|
||||||
|
|
||||||
|
module.addCSourceFile(.{ .file = b.path("libs/stb_image/stb_image.c") });
|
||||||
|
module.addIncludePath(b.path("libs/stb_image/"));
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildCuteAseprite(b: *std.Build, raylib_dep: *std.Build.Dependency) *std.Build.Module {
|
||||||
|
const module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("libs/cute_aseprite/root.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
module.addCSourceFile(.{ .file = b.path("libs/cute_aseprite/cute_aseprite.c") });
|
||||||
|
module.addIncludePath(b.path("libs/cute_aseprite/"));
|
||||||
|
module.linkLibrary(raylib_dep.artifact("raylib"));
|
||||||
|
module.addImport("raylib", raylib_dep.module("raylib"));
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const known_folders = b.dependency("known-folders", .{}).module("known-folders");
|
||||||
|
const ini = b.dependency("ini", .{}).module("ini");
|
||||||
|
|
||||||
const raylib_dep = b.dependency("raylib-zig", .{
|
const raylib_dep = b.dependency("raylib-zig", .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stb_image_lib = buildStbImage(b);
|
||||||
|
const cute_aseprite_lib = buildCuteAseprite(b, raylib_dep);
|
||||||
|
|
||||||
|
const png_to_icon_tool = b.addExecutable(.{
|
||||||
|
.name = "png-to-icon",
|
||||||
|
.root_source_file = b.path("tools/png-to-icon.zig"),
|
||||||
|
.target = target,
|
||||||
|
});
|
||||||
|
png_to_icon_tool.root_module.addImport("stb_image", stb_image_lib);
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "daq-view",
|
.name = "daq-view",
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
@ -17,34 +107,28 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
exe.addIncludePath(b.path("src"));
|
|
||||||
exe.addCSourceFile(.{
|
|
||||||
.file = b.path("src/cute_aseprite.c")
|
|
||||||
});
|
|
||||||
exe.linkLibrary(raylib_dep.artifact("raylib"));
|
exe.linkLibrary(raylib_dep.artifact("raylib"));
|
||||||
exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
||||||
|
exe.root_module.addImport("stb_image", stb_image_lib);
|
||||||
|
exe.root_module.addImport("cute_aseprite", cute_aseprite_lib);
|
||||||
|
exe.root_module.addImport("known-folders", known_folders);
|
||||||
|
exe.root_module.addImport("ini", ini);
|
||||||
|
|
||||||
const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP");
|
const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP");
|
||||||
exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) });
|
exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) });
|
||||||
|
|
||||||
const lib_folder_name = if (target.result.ptrBitWidth() == 64) "lib64" else "lib32";
|
|
||||||
exe.addLibraryPath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, lib_folder_name, "msvc" }) });
|
|
||||||
|
|
||||||
exe.linkSystemLibrary("nidaqmx");
|
|
||||||
|
|
||||||
// TODO: Emebed all assets using build.zig
|
// TODO: Emebed all assets using build.zig
|
||||||
// https://github.com/ziglang/zig/issues/14637#issuecomment-1428689051
|
// https://github.com/ziglang/zig/issues/14637#issuecomment-1428689051
|
||||||
|
|
||||||
if (target.result.os.tag == .windows) {
|
if (target.result.os.tag == .windows) {
|
||||||
exe.subsystem = if (optimize == .Debug) .Console else .Windows;
|
exe.subsystem = if (optimize == .Debug) .Console else .Windows;
|
||||||
|
|
||||||
const resource_file = b.addWriteFiles();
|
const png_to_icon_step = b.addRunArtifact(png_to_icon_tool);
|
||||||
|
png_to_icon_step.addFileArg(b.path("src/assets/icon.png"));
|
||||||
|
const icon_file = png_to_icon_step.addOutputFileArg("icon.ico");
|
||||||
|
|
||||||
// https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/
|
const add_icon_step = AddExecutableIcon.init(exe, icon_file);
|
||||||
// TODO: Generate icon file at build time
|
exe.step.dependOn(&add_icon_step.step);
|
||||||
exe.addWin32ResourceFile(.{
|
|
||||||
.file = resource_file.add("daq-view.rc", "IDI_ICON ICON \"./src/assets/icon.ico\""),
|
|
||||||
});
|
|
||||||
|
|
||||||
exe.linkSystemLibrary("Comdlg32");
|
exe.linkSystemLibrary("Comdlg32");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,14 @@
|
|||||||
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
|
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
|
||||||
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
|
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
|
||||||
},
|
},
|
||||||
|
.@"known-folders" = .{
|
||||||
|
.url = "git+https://github.com/ziglibs/known-folders.git#1cceeb70e77dec941a4178160ff6c8d05a74de6f",
|
||||||
|
.hash = "12205f5e7505c96573f6fc5144592ec38942fb0a326d692f9cddc0c7dd38f9028f29",
|
||||||
|
},
|
||||||
|
.@"ini" = .{
|
||||||
|
.url = "https://github.com/ziglibs/ini/archive/e18d36665905c1e7ba0c1ce3e8780076b33e3002.tar.gz",
|
||||||
|
.hash = "1220b0979ea9891fa4aeb85748fc42bc4b24039d9c99a4d65d893fb1c83e921efad8",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|||||||
28
libs/stb_image/root.zig
Normal file
28
libs/stb_image/root.zig
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
extern fn zig_stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, comp: ?*i32, req_comp: i32) callconv(.C) ?[*]u8;
|
||||||
|
extern fn zig_stbi_image_free(buffer: ?*anyopaque) callconv(.C) void;
|
||||||
|
|
||||||
|
pub const Image = struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
rgba: []u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: Image) void {
|
||||||
|
zig_stbi_image_free(self.rgba.ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn load(buffer: []const u8) !Image {
|
||||||
|
var width: i32 = 0;
|
||||||
|
var height: i32 = 0;
|
||||||
|
const image_rgba = zig_stbi_load_from_memory(buffer.ptr, @intCast(buffer.len), &width, &height, null, 4);
|
||||||
|
if (image_rgba == null) {
|
||||||
|
return error.PNGDecode;
|
||||||
|
}
|
||||||
|
errdefer zig_stbi_image_free(image_rgba);
|
||||||
|
|
||||||
|
const byte_count: u32 = @intCast(width * height * 4);
|
||||||
|
return Image{ .width = @intCast(width), .height = @intCast(height), .rgba = image_rgba.?[0..byte_count] };
|
||||||
|
}
|
||||||
23
libs/stb_image/stb_image.c
Normal file
23
libs/stb_image/stb_image.c
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// TODO: Use zig allocators
|
||||||
|
|
||||||
|
extern void *stb_image_zig_malloc(uint32_t amount);
|
||||||
|
extern void *stb_image_zig_realloc(void *mem, uint32_t amount);
|
||||||
|
extern void stb_image_zig_free(void *mem);
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#define STBI_NO_STDIO
|
||||||
|
#define STBI_ONLY_PNG
|
||||||
|
#define STB_IMAGE_STATIC
|
||||||
|
#include "stb_image.h"
|
||||||
|
|
||||||
|
void zig_stbi_image_free(void *retval_from_stbi_load)
|
||||||
|
{
|
||||||
|
return stbi_image_free(retval_from_stbi_load);
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_uc *zig_stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
|
||||||
|
{
|
||||||
|
return stbi_load_from_memory(buffer, len, x, y, comp, req_comp);
|
||||||
|
}
|
||||||
7988
libs/stb_image/stb_image.h
Normal file
7988
libs/stb_image/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
440
src/app.zig
440
src/app.zig
@ -5,10 +5,10 @@ const UI = @import("./ui.zig");
|
|||||||
const Platform = @import("./platform.zig");
|
const Platform = @import("./platform.zig");
|
||||||
const Assets = @import("./assets.zig");
|
const Assets = @import("./assets.zig");
|
||||||
const Graph = @import("./graph.zig");
|
const Graph = @import("./graph.zig");
|
||||||
const NIDaq = @import("ni-daq.zig");
|
const NIDaq = @import("ni-daq/root.zig");
|
||||||
const rect_utils = @import("./rect-utils.zig");
|
const rect_utils = @import("./rect-utils.zig");
|
||||||
const remap = @import("./utils.zig").remap;
|
const remap = @import("./utils.zig").remap;
|
||||||
const TaskPool = @import("./task-pool.zig");
|
const TaskPool = @import("ni-daq/task-pool.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.app);
|
const log = std.log.scoped(.app);
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
@ -92,23 +92,42 @@ allocator: std.mem.Allocator,
|
|||||||
|
|
||||||
ui: UI,
|
ui: UI,
|
||||||
channel_views: std.BoundedArray(ChannelView, max_channels) = .{},
|
channel_views: std.BoundedArray(ChannelView, max_channels) = .{},
|
||||||
ni_daq: NIDaq,
|
|
||||||
task_pool: TaskPool,
|
|
||||||
loaded_files: [max_channels]?FileChannel = .{ null } ** max_channels,
|
loaded_files: [max_channels]?FileChannel = .{ null } ** max_channels,
|
||||||
device_channels: [max_channels]?DeviceChannel = .{ null } ** max_channels,
|
device_channels: [max_channels]?DeviceChannel = .{ null } ** max_channels,
|
||||||
|
|
||||||
|
ni_daq_api: ?NIDaq.Api = null,
|
||||||
|
ni_daq: ?NIDaq = null,
|
||||||
|
task_pool: TaskPool,
|
||||||
|
|
||||||
shown_window: enum {
|
shown_window: enum {
|
||||||
channels,
|
channels,
|
||||||
add_from_device
|
add_from_device
|
||||||
} = .channels,
|
} = .channels,
|
||||||
|
|
||||||
|
shown_modal: ?union(enum) {
|
||||||
|
no_library_error,
|
||||||
|
library_version_error: std.SemanticVersion,
|
||||||
|
library_version_warning: std.SemanticVersion
|
||||||
|
} = null,
|
||||||
|
|
||||||
device_filter: NIDaq.BoundedDeviceName = .{},
|
device_filter: NIDaq.BoundedDeviceName = .{},
|
||||||
show_voltage_analog_inputs: bool = true,
|
show_voltage_analog_inputs: bool = true,
|
||||||
show_voltage_analog_outputs: bool = true,
|
show_voltage_analog_outputs: bool = true,
|
||||||
selected_channels: std.BoundedArray([:0]u8, max_channels) = .{},
|
selected_channels: std.BoundedArray([:0]u8, max_channels) = .{},
|
||||||
|
|
||||||
pub fn init(self: *App, allocator: std.mem.Allocator) !void {
|
pub fn init(self: *App, allocator: std.mem.Allocator) !void {
|
||||||
var ni_daq = try NIDaq.init(allocator, .{
|
self.* = App{
|
||||||
|
.allocator = allocator,
|
||||||
|
.ui = UI.init(allocator),
|
||||||
|
.task_pool = undefined
|
||||||
|
};
|
||||||
|
errdefer if (self.ni_daq_api != null) self.ni_daq_api.?.deinit();
|
||||||
|
errdefer if (self.ni_daq != null) self.ni_daq.?.deinit(allocator);
|
||||||
|
|
||||||
|
if (NIDaq.Api.init()) |ni_daq_api| {
|
||||||
|
self.ni_daq_api = ni_daq_api;
|
||||||
|
|
||||||
|
const ni_daq = try NIDaq.init(allocator, &self.ni_daq_api.?, .{
|
||||||
.max_devices = 4,
|
.max_devices = 4,
|
||||||
.max_analog_inputs = 32,
|
.max_analog_inputs = 32,
|
||||||
.max_analog_outputs = 8,
|
.max_analog_outputs = 8,
|
||||||
@ -117,22 +136,35 @@ pub fn init(self: *App, allocator: std.mem.Allocator) !void {
|
|||||||
.max_analog_input_voltage_ranges = 4,
|
.max_analog_input_voltage_ranges = 4,
|
||||||
.max_analog_output_voltage_ranges = 4
|
.max_analog_output_voltage_ranges = 4
|
||||||
});
|
});
|
||||||
errdefer ni_daq.deinit(allocator);
|
self.ni_daq = ni_daq;
|
||||||
|
|
||||||
self.* = App{
|
const installed_version = try ni_daq.version();
|
||||||
.allocator = allocator,
|
if (installed_version.order(NIDaq.Api.min_version) == .lt) {
|
||||||
.ui = UI.init(allocator),
|
self.shown_modal = .{ .library_version_warning = installed_version };
|
||||||
.ni_daq = ni_daq,
|
}
|
||||||
.task_pool = undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
try TaskPool.init(&self.task_pool, allocator, &self.ni_daq);
|
} else |e| {
|
||||||
|
log.err("Failed to load NI-Daq library: {any}", .{e});
|
||||||
|
|
||||||
|
switch (e) {
|
||||||
|
error.LibraryNotFound => {
|
||||||
|
self.shown_modal = .no_library_error;
|
||||||
|
},
|
||||||
|
error.SymbolNotFound => {
|
||||||
|
if (NIDaq.Api.version()) |version| {
|
||||||
|
self.shown_modal = .{ .library_version_error = version };
|
||||||
|
} else |_| {
|
||||||
|
self.shown_modal = .no_library_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try TaskPool.init(&self.task_pool, allocator);
|
||||||
errdefer self.task_pool.deinit();
|
errdefer self.task_pool.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *App) void {
|
pub fn deinit(self: *App) void {
|
||||||
self.task_pool.deinit();
|
|
||||||
|
|
||||||
for (self.channel_views.slice()) |*channel| {
|
for (self.channel_views.slice()) |*channel| {
|
||||||
channel.view_cache.deinit();
|
channel.view_cache.deinit();
|
||||||
}
|
}
|
||||||
@ -157,39 +189,9 @@ pub fn deinit(self: *App) void {
|
|||||||
self.selected_channels.len = 0;
|
self.selected_channels.len = 0;
|
||||||
|
|
||||||
self.ui.deinit();
|
self.ui.deinit();
|
||||||
self.ni_daq.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn showButton(self: *App, text: []const u8) UI.Interaction {
|
self.task_pool.deinit();
|
||||||
var button = self.ui.newWidget(self.ui.keyFromString(text));
|
if (self.ni_daq) |*ni_daq| ni_daq.deinit(self.allocator);
|
||||||
button.border = srcery.bright_blue;
|
|
||||||
button.padding.vertical(8);
|
|
||||||
button.padding.horizontal(16);
|
|
||||||
button.flags.insert(.clickable);
|
|
||||||
button.size = .{
|
|
||||||
.x = .{ .text = {} },
|
|
||||||
.y = .{ .text = {} },
|
|
||||||
};
|
|
||||||
|
|
||||||
const interaction = self.ui.getInteraction(button);
|
|
||||||
var text_color: rl.Color = undefined;
|
|
||||||
if (interaction.held_down) {
|
|
||||||
button.background = srcery.hard_black;
|
|
||||||
text_color = srcery.white;
|
|
||||||
} else if (interaction.hovering) {
|
|
||||||
button.background = srcery.bright_black;
|
|
||||||
text_color = srcery.bright_white;
|
|
||||||
} else {
|
|
||||||
button.background = srcery.blue;
|
|
||||||
text_color = srcery.bright_white;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.text = .{
|
|
||||||
.content = text,
|
|
||||||
.color = text_color
|
|
||||||
};
|
|
||||||
|
|
||||||
return interaction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readSamplesFromFile(allocator: std.mem.Allocator, file: std.fs.File) ![]f64 {
|
fn readSamplesFromFile(allocator: std.mem.Allocator, file: std.fs.File) ![]f64 {
|
||||||
@ -272,6 +274,8 @@ pub fn appendChannelFromFile(self: *App, path: []const u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
|
pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
|
||||||
|
const ni_daq = &(self.ni_daq orelse return);
|
||||||
|
|
||||||
const device_channel_index = findFreeSlot(DeviceChannel, &self.device_channels) orelse return error.DeviceChannelLimitReached;
|
const device_channel_index = findFreeSlot(DeviceChannel, &self.device_channels) orelse return error.DeviceChannelLimitReached;
|
||||||
|
|
||||||
const name_buff = try DeviceChannel.Name.fromSlice(channel_name);
|
const name_buff = try DeviceChannel.Name.fromSlice(channel_name);
|
||||||
@ -283,17 +287,17 @@ pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
|
|||||||
|
|
||||||
var min_value: f64 = 0;
|
var min_value: f64 = 0;
|
||||||
var max_value: f64 = 1;
|
var max_value: f64 = 1;
|
||||||
const voltage_ranges = try self.ni_daq.listDeviceAOVoltageRanges(device_z);
|
const voltage_ranges = try ni_daq.listDeviceAOVoltageRanges(device_z);
|
||||||
if (voltage_ranges.len > 0) {
|
if (voltage_ranges.len > 0) {
|
||||||
min_value = voltage_ranges[0].low;
|
min_value = voltage_ranges[0].low;
|
||||||
max_value = voltage_ranges[0].high;
|
max_value = voltage_ranges[0].high;
|
||||||
}
|
}
|
||||||
|
|
||||||
const max_sample_rate = try self.ni_daq.getMaxSampleRate(channel_name_z);
|
const max_sample_rate = try ni_daq.getMaxSampleRate(channel_name_z);
|
||||||
|
|
||||||
self.device_channels[device_channel_index] = DeviceChannel{
|
self.device_channels[device_channel_index] = DeviceChannel{
|
||||||
.name = name_buff,
|
.name = name_buff,
|
||||||
.min_sample_rate = self.ni_daq.getMinSampleRate(channel_name_z) catch max_sample_rate,
|
.min_sample_rate = ni_daq.getMinSampleRate(channel_name_z) catch max_sample_rate,
|
||||||
.max_sample_rate = max_sample_rate,
|
.max_sample_rate = max_sample_rate,
|
||||||
.min_value = min_value,
|
.min_value = min_value,
|
||||||
.max_value = max_value,
|
.max_value = max_value,
|
||||||
@ -334,6 +338,17 @@ fn getChannelSource(self: *App, channel_view: *ChannelView) ?ChannelView.SourceO
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn findChannelIndexByName(haystack: []const [:0]const u8, needle: [:0]const u8) ?usize {
|
||||||
|
for (0.., haystack) |i, item| {
|
||||||
|
if (std.mem.eql(u8, item, needle)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------- GUI -------------------------------------------- //
|
||||||
|
|
||||||
fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count: f32) void {
|
fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count: f32) void {
|
||||||
const min_visible_samples = 1; // sample_count*0.02;
|
const min_visible_samples = 1; // sample_count*0.02;
|
||||||
|
|
||||||
@ -347,26 +362,23 @@ fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count
|
|||||||
|
|
||||||
const minimap_rect = minimap_box.computedRect();
|
const minimap_rect = minimap_box.computedRect();
|
||||||
|
|
||||||
const middle_box = self.ui.newBoxFromString("Middle knob");
|
const middle_box = self.ui.clickableBox("Middle knob");
|
||||||
{
|
{
|
||||||
middle_box.flags.insert(.clickable);
|
|
||||||
middle_box.flags.insert(.draggable_x);
|
middle_box.flags.insert(.draggable_x);
|
||||||
middle_box.background = rl.Color.black.alpha(0.5);
|
middle_box.background = rl.Color.black.alpha(0.5);
|
||||||
middle_box.size.y = UI.Size.pixels(32, 1);
|
middle_box.size.y = UI.Size.pixels(32, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const left_knob_box = self.ui.newBoxFromString("Left knob");
|
const left_knob_box = self.ui.clickableBox("Left knob");
|
||||||
{
|
{
|
||||||
left_knob_box.flags.insert(.clickable);
|
|
||||||
left_knob_box.flags.insert(.draggable_x);
|
left_knob_box.flags.insert(.draggable_x);
|
||||||
left_knob_box.background = rl.Color.black.alpha(0.5);
|
left_knob_box.background = rl.Color.black.alpha(0.5);
|
||||||
left_knob_box.size.x = UI.Size.pixels(8, 1);
|
left_knob_box.size.x = UI.Size.pixels(8, 1);
|
||||||
left_knob_box.size.y = UI.Size.pixels(32, 1);
|
left_knob_box.size.y = UI.Size.pixels(32, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const right_knob_box = self.ui.newBoxFromString("Right knob");
|
const right_knob_box = self.ui.clickableBox("Right knob");
|
||||||
{
|
{
|
||||||
right_knob_box.flags.insert(.clickable);
|
|
||||||
right_knob_box.flags.insert(.draggable_x);
|
right_knob_box.flags.insert(.draggable_x);
|
||||||
right_knob_box.background = rl.Color.black.alpha(0.5);
|
right_knob_box.background = rl.Color.black.alpha(0.5);
|
||||||
right_knob_box.size.x = UI.Size.pixels(8, 1);
|
right_knob_box.size.x = UI.Size.pixels(8, 1);
|
||||||
@ -460,15 +472,11 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
|
|||||||
if (source == .device) {
|
if (source == .device) {
|
||||||
const device_channel = source.device;
|
const device_channel = source.device;
|
||||||
|
|
||||||
{
|
if (self.ni_daq) |*ni_daq| {
|
||||||
const record_button = self.ui.newBoxFromString("Record");
|
const record_button = self.ui.button(.text, "Record");
|
||||||
record_button.flags.insert(.clickable);
|
|
||||||
record_button.size.x = UI.Size.text(1, 0);
|
|
||||||
record_button.size.y = UI.Size.percent(1, 0);
|
record_button.size.y = UI.Size.percent(1, 0);
|
||||||
|
|
||||||
if (device_channel.active_task == null) {
|
if (device_channel.active_task != null) {
|
||||||
record_button.setText(.text, "Record");
|
|
||||||
} else {
|
|
||||||
record_button.setText(.text, "Stop");
|
record_button.setText(.text, "Stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,6 +488,7 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
|
|||||||
} else {
|
} else {
|
||||||
const channel_name = device_channel.name.buffer[0..device_channel.name.len :0];
|
const channel_name = device_channel.name.buffer[0..device_channel.name.len :0];
|
||||||
device_channel.active_task = try self.task_pool.launchAIVoltageChannel(
|
device_channel.active_task = try self.task_pool.launchAIVoltageChannel(
|
||||||
|
ni_daq,
|
||||||
&device_channel.mutex,
|
&device_channel.mutex,
|
||||||
&device_channel.samples,
|
&device_channel.samples,
|
||||||
.{
|
.{
|
||||||
@ -499,11 +508,11 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const follow_button = self.ui.newBoxFromString("Follow");
|
const follow_button = self.ui.button(.text, "Follow");
|
||||||
follow_button.flags.insert(.clickable);
|
|
||||||
follow_button.size.x = UI.Size.text(1, 0);
|
|
||||||
follow_button.size.y = UI.Size.percent(1, 0);
|
follow_button.size.y = UI.Size.percent(1, 0);
|
||||||
follow_button.setText(.text, if (channel_view.follow) "Unfollow" else "Follow");
|
if (channel_view.follow) {
|
||||||
|
follow_button.setText(.text, "Unfollow");
|
||||||
|
}
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(follow_button);
|
const signal = self.ui.signalFromBox(follow_button);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -544,13 +553,15 @@ fn showChannelsWindow(self: *App) !void {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const prompt_box = self.ui.newBoxFromString("Add prompt");
|
const prompt_box = self.ui.newBoxFromString("Add prompt");
|
||||||
prompt_box.layout_axis = .X;
|
|
||||||
prompt_box.size.x = UI.Size.percent(1, 0);
|
prompt_box.size.x = UI.Size.percent(1, 0);
|
||||||
prompt_box.size.y = UI.Size.pixels(150, 1);
|
prompt_box.size.y = UI.Size.percent(1, 1);
|
||||||
self.ui.pushParent(prompt_box);
|
self.ui.pushParent(prompt_box);
|
||||||
defer self.ui.popParent();
|
defer self.ui.popParent();
|
||||||
|
|
||||||
self.ui.spacer(.{ .x = UI.Size.percent(1, 0) });
|
const center_box = self.ui.pushCenterBox();
|
||||||
|
defer self.ui.popCenterBox();
|
||||||
|
center_box.layout_axis = .X;
|
||||||
|
center_box.layout_gap = 32;
|
||||||
|
|
||||||
const from_file_button = self.ui.button(.text, "Add from file");
|
const from_file_button = self.ui.button(.text, "Add from file");
|
||||||
from_file_button.background = srcery.green;
|
from_file_button.background = srcery.green;
|
||||||
@ -558,28 +569,17 @@ fn showChannelsWindow(self: *App) !void {
|
|||||||
log.debug("TODO: Not implemented", .{});
|
log.debug("TODO: Not implemented", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ui.spacer(.{ .x = UI.Size.pixels(32, 1) });
|
|
||||||
|
|
||||||
const from_device_button = self.ui.button(.text, "Add from device");
|
const from_device_button = self.ui.button(.text, "Add from device");
|
||||||
from_device_button.background = srcery.green;
|
from_device_button.background = srcery.green;
|
||||||
if (self.ui.signalFromBox(from_device_button).clicked()) {
|
if (self.ui.signalFromBox(from_device_button).clicked()) {
|
||||||
log.debug("TODO: Not implemented", .{});
|
log.debug("TODO: Not implemented", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ui.spacer(.{ .x = UI.Size.percent(1, 0) });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn findChannelIndexByName(haystack: []const [:0]const u8, needle: [:0]const u8) ?usize {
|
|
||||||
for (0.., haystack) |i, item| {
|
|
||||||
if (std.mem.eql(u8, item, needle)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn showAddFromDeviceWindow(self: *App) !void {
|
fn showAddFromDeviceWindow(self: *App) !void {
|
||||||
|
const ni_daq = &(self.ni_daq orelse return);
|
||||||
|
|
||||||
const window = self.ui.newBoxFromString("Device window");
|
const window = self.ui.newBoxFromString("Device window");
|
||||||
window.size.x = UI.Size.percent(1, 0);
|
window.size.x = UI.Size.percent(1, 0);
|
||||||
window.size.y = UI.Size.percent(1, 0);
|
window.size.y = UI.Size.percent(1, 0);
|
||||||
@ -595,12 +595,10 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
self.ui.pushParent(filters_box);
|
self.ui.pushParent(filters_box);
|
||||||
defer self.ui.popParent();
|
defer self.ui.popParent();
|
||||||
|
|
||||||
for (try self.ni_daq.listDeviceNames()) |device| {
|
for (try ni_daq.listDeviceNames()) |device| {
|
||||||
const device_box = self.ui.newBoxFromString(device);
|
const device_box = self.ui.button(.text, device);
|
||||||
device_box.flags.insert(.clickable);
|
|
||||||
device_box.size.x = UI.Size.text(2, 1);
|
device_box.size.x = UI.Size.text(2, 1);
|
||||||
device_box.size.y = UI.Size.text(2, 1);
|
device_box.size.y = UI.Size.text(2, 1);
|
||||||
device_box.setText(.text, device);
|
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(device_box);
|
const signal = self.ui.signalFromBox(device_box);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -609,37 +607,33 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const toggle_inputs_box = self.ui.newBoxFromString("Toggle inputs");
|
const toggle_inputs_box = self.ui.button(.text, "Toggle inputs");
|
||||||
toggle_inputs_box.flags.insert(.clickable);
|
|
||||||
toggle_inputs_box.size.x = UI.Size.text(2, 1);
|
toggle_inputs_box.size.x = UI.Size.text(2, 1);
|
||||||
toggle_inputs_box.size.y = UI.Size.text(2, 1);
|
toggle_inputs_box.size.y = UI.Size.text(2, 1);
|
||||||
toggle_inputs_box.setText(.text, if (self.show_voltage_analog_inputs) "Hide inputs" else "Show inputs");
|
toggle_inputs_box.setText(.text, if (self.show_voltage_analog_inputs) "Hide inputs" else "Show inputs");
|
||||||
const signal = self.ui.signalFromBox(toggle_inputs_box);
|
|
||||||
if (signal.clicked()) {
|
if (self.ui.signalFromBox(toggle_inputs_box).clicked()) {
|
||||||
self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs;
|
self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const toggle_outputs_box = self.ui.newBoxFromString("Toggle outputs");
|
const toggle_outputs_box = self.ui.button(.text, "Toggle outputs");
|
||||||
toggle_outputs_box.flags.insert(.clickable);
|
|
||||||
toggle_outputs_box.size.x = UI.Size.text(2, 1);
|
toggle_outputs_box.size.x = UI.Size.text(2, 1);
|
||||||
toggle_outputs_box.size.y = UI.Size.text(2, 1);
|
toggle_outputs_box.size.y = UI.Size.text(2, 1);
|
||||||
toggle_outputs_box.setText(.text, if (self.show_voltage_analog_outputs) "Hide outputs" else "Show outputs");
|
toggle_outputs_box.setText(.text, if (self.show_voltage_analog_outputs) "Hide outputs" else "Show outputs");
|
||||||
const signal = self.ui.signalFromBox(toggle_outputs_box);
|
|
||||||
if (signal.clicked()) {
|
if (self.ui.signalFromBox(toggle_outputs_box).clicked()) {
|
||||||
self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs;
|
self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const add_button = self.ui.newBoxFromString("Add");
|
const add_button = self.ui.button(.text, "Add selected");
|
||||||
add_button.flags.insert(.clickable);
|
|
||||||
add_button.size.x = UI.Size.text(2, 1);
|
add_button.size.x = UI.Size.text(2, 1);
|
||||||
add_button.size.y = UI.Size.text(2, 1);
|
add_button.size.y = UI.Size.text(2, 1);
|
||||||
add_button.setText(.text, "Add selected");
|
|
||||||
const signal = self.ui.signalFromBox(add_button);
|
if (self.ui.signalFromBox(add_button).clicked()) {
|
||||||
if (signal.clicked()) {
|
|
||||||
const selected_devices = self.selected_channels.constSlice();
|
const selected_devices = self.selected_channels.constSlice();
|
||||||
|
|
||||||
for (selected_devices) |channel| {
|
for (selected_devices) |channel| {
|
||||||
@ -668,21 +662,21 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
self.device_filter.buffer[0..self.device_filter.len :0]
|
self.device_filter.buffer[0..self.device_filter.len :0]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
devices = try self.ni_daq.listDeviceNames();
|
devices = try ni_daq.listDeviceNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (devices) |device| {
|
for (devices) |device| {
|
||||||
var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
|
var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
|
||||||
if (self.show_voltage_analog_inputs) {
|
if (self.show_voltage_analog_inputs) {
|
||||||
if (try self.ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
|
if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
|
||||||
ai_voltage_physical_channels = try self.ni_daq.listDeviceAIPhysicalChannels(device);
|
ai_voltage_physical_channels = try ni_daq.listDeviceAIPhysicalChannels(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ao_physical_channels: []const [:0]const u8 = &.{};
|
var ao_physical_channels: []const [:0]const u8 = &.{};
|
||||||
if (self.show_voltage_analog_outputs) {
|
if (self.show_voltage_analog_outputs) {
|
||||||
if (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
|
if (try ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
|
||||||
ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device);
|
ao_physical_channels = try ni_daq.listDeviceAOPhysicalChannels(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -690,11 +684,7 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
for (channels) |channel| {
|
for (channels) |channel| {
|
||||||
const selected_channels_slice = self.selected_channels.constSlice();
|
const selected_channels_slice = self.selected_channels.constSlice();
|
||||||
|
|
||||||
const channel_box = self.ui.newBoxFromString(channel);
|
const channel_box = self.ui.button(.text, channel);
|
||||||
channel_box.flags.insert(.clickable);
|
|
||||||
channel_box.size.x = UI.Size.text(1, 1);
|
|
||||||
channel_box.size.y = UI.Size.text(0.5, 1);
|
|
||||||
channel_box.setText(.text, channel);
|
|
||||||
|
|
||||||
if (findChannelIndexByName(selected_channels_slice, channel) != null) {
|
if (findChannelIndexByName(selected_channels_slice, channel) != null) {
|
||||||
channel_box.background = srcery.xgray3;
|
channel_box.background = srcery.xgray3;
|
||||||
@ -717,7 +707,6 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
|
|
||||||
fn showToolbar(self: *App) void {
|
fn showToolbar(self: *App) void {
|
||||||
const toolbar = self.ui.newBoxFromString("Toolbar");
|
const toolbar = self.ui.newBoxFromString("Toolbar");
|
||||||
toolbar.flags.insert(.clickable);
|
|
||||||
toolbar.background = rl.Color.green;
|
toolbar.background = rl.Color.green;
|
||||||
toolbar.layout_axis = .X;
|
toolbar.layout_axis = .X;
|
||||||
toolbar.size = .{
|
toolbar.size = .{
|
||||||
@ -728,14 +717,9 @@ fn showToolbar(self: *App) void {
|
|||||||
defer self.ui.popParent();
|
defer self.ui.popParent();
|
||||||
|
|
||||||
{
|
{
|
||||||
const box = self.ui.newBoxFromString("Add from file");
|
const box = self.ui.button(.text, "Add from file");
|
||||||
box.flags.insert(.clickable);
|
|
||||||
box.background = rl.Color.red;
|
box.background = rl.Color.red;
|
||||||
box.size = .{
|
box.size.y = UI.Size.percent(1, 1);
|
||||||
.x = UI.Size.text(2, 1),
|
|
||||||
.y = UI.Size.percent(1, 1)
|
|
||||||
};
|
|
||||||
box.setText(.text, "Add from file",);
|
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(box);
|
const signal = self.ui.signalFromBox(box);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -752,14 +736,9 @@ fn showToolbar(self: *App) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const box = self.ui.newBoxFromString("Add from device");
|
const box = self.ui.button(.text, "Add from device");
|
||||||
box.flags.insert(.clickable);
|
|
||||||
box.background = rl.Color.lime;
|
box.background = rl.Color.lime;
|
||||||
box.size = .{
|
box.size.y = UI.Size.percent(1, 1);
|
||||||
.x = UI.Size.text(2, 1),
|
|
||||||
.y = UI.Size.percent(1, 1)
|
|
||||||
};
|
|
||||||
box.setText(.text, "Add from device");
|
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(box);
|
const signal = self.ui.signalFromBox(box);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -772,6 +751,186 @@ fn showToolbar(self: *App) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn showModalNoLibraryError(self: *App) void {
|
||||||
|
const modal = self.ui.getParent().?;
|
||||||
|
|
||||||
|
modal.layout_axis = .Y;
|
||||||
|
modal.size = .{
|
||||||
|
.x = UI.Size.pixels(400, 1),
|
||||||
|
.y = UI.Size.pixels(320, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
const text = self.ui.newBoxFromString("Text");
|
||||||
|
text.flags.insert(.text_wrapping);
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.appendText("PALA, PALA! Aš neradau būtinos bibliotekos ant kompiuterio. Programa vis dar veiks, bet ");
|
||||||
|
text.appendText("dauguma funkcijų bus paslėptos. Susirask iternete \"NI MAX\" ir instaliuok. Štai nuorada ");
|
||||||
|
text.appendText("į gidą.");
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const link = self.ui.newBoxFromString("Link");
|
||||||
|
link.flags.insert(.clickable);
|
||||||
|
link.flags.insert(.hover_mouse_hand);
|
||||||
|
link.flags.insert(.text_underline);
|
||||||
|
link.size.x = UI.Size.text(1, 1);
|
||||||
|
link.size.y = UI.Size.text(1, 1);
|
||||||
|
link.setText(
|
||||||
|
.text,
|
||||||
|
"Nuorada į gidą"
|
||||||
|
);
|
||||||
|
link.text.?.color = srcery.blue;
|
||||||
|
|
||||||
|
const signal = self.ui.signalFromBox(link);
|
||||||
|
if (signal.clicked()) {
|
||||||
|
rl.openURL("https://knowledge.ni.com/KnowledgeArticleDetails?id=kA03q000000YGQwCAO&l=en-LT");
|
||||||
|
}
|
||||||
|
if (self.ui.isBoxHot(link)) {
|
||||||
|
link.text.?.color = srcery.bright_blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const btn = self.ui.button(.text, "Supratau");
|
||||||
|
btn.background = srcery.green;
|
||||||
|
if (self.ui.signalFromBox(btn).clicked()) {
|
||||||
|
self.shown_modal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showModalLibraryVersionError(self: *App) void {
|
||||||
|
assert(self.shown_modal.? == .library_version_error);
|
||||||
|
|
||||||
|
const installed_version = self.shown_modal.?.library_version_error;
|
||||||
|
const modal = self.ui.getParent().?;
|
||||||
|
|
||||||
|
modal.layout_axis = .Y;
|
||||||
|
modal.size = .{
|
||||||
|
.x = UI.Size.pixels(400, 1),
|
||||||
|
.y = UI.Size.pixels(320, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.flags.insert(.text_wrapping);
|
||||||
|
text.flags.insert(.text_left_align);
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.appendText("Ooo ne! Reikalinga biblioteka surasta, bet nesurastos reikalingos funkcijos. ");
|
||||||
|
text.appendText("Susitikrink, kad turi pakankamai naują versiją NI MAX instaliuota.");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Instaliuota versija: {}", .{installed_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Rekomenduotina versija: {}", .{NIDaq.Api.min_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const btn = self.ui.button(.text, "Supratau");
|
||||||
|
btn.background = srcery.green;
|
||||||
|
if (self.ui.signalFromBox(btn).clicked()) {
|
||||||
|
self.shown_modal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showModalLibraryVersionWarning(self: *App) void {
|
||||||
|
assert(self.shown_modal.? == .library_version_warning);
|
||||||
|
|
||||||
|
const installed_version = self.shown_modal.?.library_version_warning;
|
||||||
|
const modal = self.ui.getParent().?;
|
||||||
|
|
||||||
|
modal.layout_axis = .Y;
|
||||||
|
modal.size = .{
|
||||||
|
.x = UI.Size.pixels(400, 1),
|
||||||
|
.y = UI.Size.pixels(320, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.flags.insert(.text_wrapping);
|
||||||
|
text.flags.insert(.text_left_align);
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.appendText("Instaliuota NI MAX versija žemesnė negu rekomenduotina versija. ");
|
||||||
|
text.appendText("Daug kas turėtų veikti, bet negaliu garantuoti kad viskas veiks. ");
|
||||||
|
text.appendText("Jeigu susidursi su problemomis kur programa sustoja veikti pabandyk atsinaujinti ");
|
||||||
|
text.appendText("NI MAX.");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Instaliuota versija: {}", .{installed_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Rekomenduotina versija: {}", .{NIDaq.Api.min_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const btn = self.ui.button(.text, "Supratau");
|
||||||
|
btn.background = srcery.green;
|
||||||
|
if (self.ui.signalFromBox(btn).clicked()) {
|
||||||
|
self.shown_modal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showModal(self: *App) void {
|
||||||
|
assert(self.shown_modal != null);
|
||||||
|
|
||||||
|
switch (self.shown_modal.?) {
|
||||||
|
.no_library_error => self.showModalNoLibraryError(),
|
||||||
|
.library_version_error => self.showModalLibraryVersionError(),
|
||||||
|
.library_version_warning => self.showModalLibraryVersionWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn updateUI(self: *App) !void {
|
fn updateUI(self: *App) !void {
|
||||||
self.ui.begin();
|
self.ui.begin();
|
||||||
defer self.ui.end();
|
defer self.ui.end();
|
||||||
@ -779,6 +938,32 @@ fn updateUI(self: *App) !void {
|
|||||||
const root_box = self.ui.getParent().?;
|
const root_box = self.ui.getParent().?;
|
||||||
root_box.layout_axis = .Y;
|
root_box.layout_axis = .Y;
|
||||||
|
|
||||||
|
var maybe_modal_overlay: ?*UI.Box = null;
|
||||||
|
|
||||||
|
if (self.shown_modal != null) {
|
||||||
|
const modal_overlay = self.ui.newBoxNoAppend(self.ui.newKeyFromString("Modal overlay"));
|
||||||
|
maybe_modal_overlay = modal_overlay;
|
||||||
|
modal_overlay.flags.insert(.clickable);
|
||||||
|
modal_overlay.flags.insert(.scrollable);
|
||||||
|
modal_overlay.background = rl.Color.black.alpha(0.5);
|
||||||
|
modal_overlay.setFixedPosition(.{ .x = 0, .y = 0 });
|
||||||
|
modal_overlay.size = .{
|
||||||
|
.x = UI.Size.percent(1, 0),
|
||||||
|
.y = UI.Size.percent(1, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.pushParent(modal_overlay);
|
||||||
|
defer self.ui.popParent();
|
||||||
|
|
||||||
|
const modal = self.ui.pushCenterBox();
|
||||||
|
defer self.ui.popCenterBox();
|
||||||
|
modal.background = srcery.hard_black;
|
||||||
|
|
||||||
|
self.showModal();
|
||||||
|
|
||||||
|
_ = self.ui.signalFromBox(modal_overlay);
|
||||||
|
}
|
||||||
|
|
||||||
self.showToolbar();
|
self.showToolbar();
|
||||||
|
|
||||||
if (self.shown_window == .channels) {
|
if (self.shown_window == .channels) {
|
||||||
@ -786,6 +971,10 @@ fn updateUI(self: *App) !void {
|
|||||||
} else if (self.shown_window == .add_from_device) {
|
} else if (self.shown_window == .add_from_device) {
|
||||||
try self.showAddFromDeviceWindow();
|
try self.showAddFromDeviceWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maybe_modal_overlay) |box| {
|
||||||
|
self.ui.appendBox(box);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *App) !void {
|
pub fn tick(self: *App) !void {
|
||||||
@ -822,7 +1011,6 @@ pub fn tick(self: *App) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// On the first frame, render the UI twice.
|
// On the first frame, render the UI twice.
|
||||||
// So that on the second pass widgets that depend on sizes from other widgets have settled
|
// So that on the second pass widgets that depend on sizes from other widgets have settled
|
||||||
if (self.ui.frame_index == 0) {
|
if (self.ui.frame_index == 0) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ const std = @import("std");
|
|||||||
const rl = @import("raylib");
|
const rl = @import("raylib");
|
||||||
const srcery = @import("./srcery.zig");
|
const srcery = @import("./srcery.zig");
|
||||||
const FontFace = @import("./font-face.zig");
|
const FontFace = @import("./font-face.zig");
|
||||||
const Aseprite = @import("./aseprite.zig");
|
const Aseprite = @import("cute_aseprite");
|
||||||
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
@ -60,12 +60,18 @@ pub fn init(allocator: std.mem.Allocator) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font {
|
fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font {
|
||||||
var codepoints: [95]i32 = undefined;
|
var codepoints: std.BoundedArray(i32, 128) = .{};
|
||||||
for (0..codepoints.len) |i| {
|
for (0..95) |i| {
|
||||||
codepoints[i] = @as(i32, @intCast(i)) + 32;
|
codepoints.appendAssumeCapacity(@as(i32, @intCast(i)) + 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loaded_font = rl.loadFontFromMemory(".ttf", ttf_data, @intCast(font_size), &codepoints);
|
const lithuanina_characters = std.unicode.Utf8View.initComptime("ąčęėįšųūžĄČĘĖĮŠŲŪŽ");
|
||||||
|
var char_iter = lithuanina_characters.iterator();
|
||||||
|
while (char_iter.nextCodepoint()) |codepoint| {
|
||||||
|
codepoints.appendAssumeCapacity(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loaded_font = rl.loadFontFromMemory(".ttf", ttf_data, @intCast(font_size), codepoints.slice());
|
||||||
if (!loaded_font.isReady()) {
|
if (!loaded_font.isReady()) {
|
||||||
return error.LoadFontFromMemory;
|
return error.LoadFontFromMemory;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 10 KiB |
Binary file not shown.
@ -134,6 +134,10 @@ pub fn measureText(self: @This(), text: []const u8) rl.Vector2 {
|
|||||||
return text_size;
|
return text_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn measureWidth(self: @This(), text: []const u8) f32 {
|
||||||
|
return self.measureText(text).x;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn drawTextCenter(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
|
pub fn drawTextCenter(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
|
||||||
const text_size = self.measureText(text);
|
const text_size = self.measureText(text);
|
||||||
const adjusted_position = rl.Vector2{
|
const adjusted_position = rl.Vector2{
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const builtin = @import("builtin");
|
|||||||
const Application = @import("./app.zig");
|
const Application = @import("./app.zig");
|
||||||
const Assets = @import("./assets.zig");
|
const Assets = @import("./assets.zig");
|
||||||
const Profiler = @import("./profiler.zig");
|
const Profiler = @import("./profiler.zig");
|
||||||
|
const Platform = @import("./platform.zig");
|
||||||
const raylib_h = @cImport({
|
const raylib_h = @cImport({
|
||||||
@cInclude("stdio.h");
|
@cInclude("stdio.h");
|
||||||
@cInclude("raylib.h");
|
@cInclude("raylib.h");
|
||||||
@ -64,6 +65,8 @@ fn raylibTraceLogCallback(logType: c_int, text: [*c]const u8, args: raylib_h.va_
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
Platform.init();
|
||||||
|
|
||||||
// TODO: Setup logging to a file
|
// TODO: Setup logging to a file
|
||||||
raylib_h.SetTraceLogCallback(raylibTraceLogCallback);
|
raylib_h.SetTraceLogCallback(raylibTraceLogCallback);
|
||||||
rl.setTraceLogLevel(toRaylibLogLevel(std.options.log_level));
|
rl.setTraceLogLevel(toRaylibLogLevel(std.options.log_level));
|
||||||
@ -153,7 +156,7 @@ pub fn main() !void {
|
|||||||
defer app.deinit();
|
defer app.deinit();
|
||||||
|
|
||||||
if (builtin.mode == .Debug) {
|
if (builtin.mode == .Debug) {
|
||||||
try app.appendChannelFromDevice("Dev1/ai0");
|
// try app.appendChannelFromDevice("Dev1/ai0");
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin");
|
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin");
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
||||||
}
|
}
|
||||||
@ -191,5 +194,5 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = @import("./ni-daq.zig");
|
_ = @import("./ni-daq/root.zig");
|
||||||
}
|
}
|
||||||
100
src/ni-daq/api.zig
Normal file
100
src/ni-daq/api.zig
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
pub const c = @cImport({
|
||||||
|
@cInclude("stdint.h");
|
||||||
|
@cDefine("__int64", "long long");
|
||||||
|
@cInclude("NIDAQmx.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
const Api = @This();
|
||||||
|
|
||||||
|
const log = std.log.scoped(.ni_daq_api);
|
||||||
|
|
||||||
|
pub const min_version = std.SemanticVersion{
|
||||||
|
.major = 24,
|
||||||
|
.minor = 8,
|
||||||
|
.patch = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Error = error {
|
||||||
|
LibraryNotFound,
|
||||||
|
SymbolNotFound
|
||||||
|
};
|
||||||
|
|
||||||
|
lib: std.DynLib, // This MUST be the first field of `Api` struct
|
||||||
|
|
||||||
|
DAQmxGetSysNIDAQMajorVersion: *const @TypeOf(c.DAQmxGetSysNIDAQMajorVersion),
|
||||||
|
DAQmxGetSysNIDAQMinorVersion: *const @TypeOf(c.DAQmxGetSysNIDAQMinorVersion),
|
||||||
|
DAQmxGetSysNIDAQUpdateVersion: *const @TypeOf(c.DAQmxGetSysNIDAQUpdateVersion),
|
||||||
|
DAQmxGetErrorString: *const @TypeOf(c.DAQmxGetErrorString),
|
||||||
|
DAQmxStopTask: *const @TypeOf(c.DAQmxStopTask),
|
||||||
|
DAQmxStartTask: *const @TypeOf(c.DAQmxStartTask),
|
||||||
|
DAQmxClearTask: *const @TypeOf(c.DAQmxClearTask),
|
||||||
|
DAQmxGetTaskName: *const @TypeOf(c.DAQmxGetTaskName),
|
||||||
|
DAQmxCfgSampClkTiming: *const @TypeOf(c.DAQmxCfgSampClkTiming),
|
||||||
|
DAQmxCreateTask: *const @TypeOf(c.DAQmxCreateTask),
|
||||||
|
DAQmxCreateAIVoltageChan: *const @TypeOf(c.DAQmxCreateAIVoltageChan),
|
||||||
|
DAQmxGetSysDevNames: *const @TypeOf(c.DAQmxGetSysDevNames),
|
||||||
|
DAQmxGetDevAIVoltageRngs: *const @TypeOf(c.DAQmxGetDevAIVoltageRngs),
|
||||||
|
DAQmxGetDevAOVoltageRngs: *const @TypeOf(c.DAQmxGetDevAOVoltageRngs),
|
||||||
|
DAQmxGetDevAIMaxSingleChanRate: *const @TypeOf(c.DAQmxGetDevAIMaxSingleChanRate),
|
||||||
|
DAQmxGetDevAOMaxRate: *const @TypeOf(c.DAQmxGetDevAOMaxRate),
|
||||||
|
DAQmxGetDevAIMinRate: *const @TypeOf(c.DAQmxGetDevAIMinRate),
|
||||||
|
DAQmxGetDevAOMinRate: *const @TypeOf(c.DAQmxGetDevAOMinRate),
|
||||||
|
DAQmxGetDevAISupportedMeasTypes: *const @TypeOf(c.DAQmxGetDevAISupportedMeasTypes),
|
||||||
|
DAQmxGetDevAOSupportedOutputTypes: *const @TypeOf(c.DAQmxGetDevAOSupportedOutputTypes),
|
||||||
|
DAQmxGetDevProductCategory: *const @TypeOf(c.DAQmxGetDevProductCategory),
|
||||||
|
DAQmxGetDevAIPhysicalChans: *const @TypeOf(c.DAQmxGetDevAIPhysicalChans),
|
||||||
|
DAQmxGetDevAOPhysicalChans: *const @TypeOf(c.DAQmxGetDevAOPhysicalChans),
|
||||||
|
DAQmxReadAnalogF64: *const @TypeOf(c.DAQmxReadAnalogF64),
|
||||||
|
|
||||||
|
pub fn init() Error!Api {
|
||||||
|
var api: Api = undefined;
|
||||||
|
|
||||||
|
api.lib = std.DynLib.open("nicaiu") catch return error.LibraryNotFound;
|
||||||
|
errdefer api.lib.close();
|
||||||
|
|
||||||
|
inline for (@typeInfo(Api).Struct.fields[1..]) |field| {
|
||||||
|
const name = std.fmt.comptimePrint("{s}\x00", .{field.name});
|
||||||
|
const name_z = name[0 .. (name.len - 1) :0];
|
||||||
|
@field(api, field.name) = api.lib.lookup(field.type, name_z) orelse {
|
||||||
|
log.err("Symbol lookup failed for {s}", .{name});
|
||||||
|
return error.SymbolNotFound;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Api) void {
|
||||||
|
self.lib.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version() !std.SemanticVersion {
|
||||||
|
var lib = std.DynLib.open("nicaiu") catch return error.LibraryNotFound;
|
||||||
|
defer lib.close();
|
||||||
|
|
||||||
|
const getMajorVersion = lib.lookup(*const @TypeOf(c.DAQmxGetSysNIDAQMajorVersion), "DAQmxGetSysNIDAQMajorVersion") orelse return error.SymbolNotFound;
|
||||||
|
const getMinorVersion = lib.lookup(*const @TypeOf(c.DAQmxGetSysNIDAQMinorVersion), "DAQmxGetSysNIDAQMinorVersion") orelse return error.SymbolNotFound;
|
||||||
|
const getUpdateVersion = lib.lookup(*const @TypeOf(c.DAQmxGetSysNIDAQUpdateVersion), "DAQmxGetSysNIDAQUpdateVersion") orelse return error.SymbolNotFound;
|
||||||
|
|
||||||
|
var major: u32 = 0;
|
||||||
|
if (getMajorVersion(&major) < 0) {
|
||||||
|
return error.GetMajorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minor: u32 = 0;
|
||||||
|
if (getMinorVersion(&minor) < 0) {
|
||||||
|
return error.GetMinorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
var update: u32 = 0;
|
||||||
|
if (getUpdateVersion(&update) < 0) {
|
||||||
|
return error.GetUpdateVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std.SemanticVersion{
|
||||||
|
.major = major,
|
||||||
|
.minor = minor,
|
||||||
|
.patch = update
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,9 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
pub const c = @cImport({
|
pub const Api = @import("./api.zig");
|
||||||
@cInclude("stdint.h");
|
pub const c = Api.c;
|
||||||
@cDefine("__int64", "long long");
|
|
||||||
@cInclude("NIDAQmx.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const log = std.log.scoped(.ni_daq);
|
const log = std.log.scoped(.ni_daq);
|
||||||
@ -38,6 +35,7 @@ pub const Options = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Task = struct {
|
pub const Task = struct {
|
||||||
|
ni_daq: *NIDaq,
|
||||||
handle: TaskHandle,
|
handle: TaskHandle,
|
||||||
|
|
||||||
name_buffer: [max_task_name_size]u8 = undefined,
|
name_buffer: [max_task_name_size]u8 = undefined,
|
||||||
@ -45,19 +43,23 @@ pub const Task = struct {
|
|||||||
dropped_samples: u32 = 0,
|
dropped_samples: u32 = 0,
|
||||||
|
|
||||||
pub fn clear(self: Task) void {
|
pub fn clear(self: Task) void {
|
||||||
logDAQmxError(c.DAQmxClearTask(self.handle));
|
// Ignore error
|
||||||
|
const api = self.ni_daq.api;
|
||||||
|
self.ni_daq.logDAQmxError(api.DAQmxClearTask(self.handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(self: *Task) ![]const u8 {
|
pub fn name(self: *Task) ![]const u8 {
|
||||||
const required_size = c.DAQmxGetTaskName(self.handle, null, 0);
|
const api = self.ni_daq.api;
|
||||||
|
|
||||||
|
const required_size = api.DAQmxGetTaskName(self.handle, null, 0);
|
||||||
assert(required_size >= 0);
|
assert(required_size >= 0);
|
||||||
|
|
||||||
if (required_size > self.name_buffer.len) {
|
if (required_size > self.name_buffer.len) {
|
||||||
return error.BufferTooSmall;
|
return error.BufferTooSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
try checkDAQmxError(
|
try self.ni_daq.checkDAQmxError(
|
||||||
c.DAQmxGetTaskName(
|
api.DAQmxGetTaskName(
|
||||||
self.handle,
|
self.handle,
|
||||||
&self.name_buffer,
|
&self.name_buffer,
|
||||||
self.name_buffer.len
|
self.name_buffer.len
|
||||||
@ -69,29 +71,33 @@ pub const Task = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(self: Task) !void {
|
pub fn start(self: Task) !void {
|
||||||
try checkDAQmxError(
|
const api = self.ni_daq.api;
|
||||||
c.DAQmxStartTask(self.handle),
|
try self.ni_daq.checkDAQmxError(
|
||||||
|
api.DAQmxStartTask(self.handle),
|
||||||
error.DAQmxStartTask
|
error.DAQmxStartTask
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(self: Task) !void {
|
pub fn stop(self: Task) !void {
|
||||||
try checkDAQmxError(
|
const api = self.ni_daq.api;
|
||||||
c.DAQmxStopTask(self.handle),
|
try self.ni_daq.checkDAQmxError(
|
||||||
|
api.DAQmxStopTask(self.handle),
|
||||||
error.DAQmxStopTask
|
error.DAQmxStopTask
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setContinousSampleRate(self: Task, sample_rate: f64) !void {
|
pub fn setContinousSampleRate(self: Task, sample_rate: f64) !void {
|
||||||
try checkDAQmxError(
|
const api = self.ni_daq.api;
|
||||||
c.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_ContSamps, 0),
|
try self.ni_daq.checkDAQmxError(
|
||||||
|
api.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_ContSamps, 0),
|
||||||
error.DAQmxCfgSampClkTiming
|
error.DAQmxCfgSampClkTiming
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setFiniteSampleRate(self: Task, sample_rate: f64, samples_per_channel: u64) !void {
|
pub fn setFiniteSampleRate(self: Task, sample_rate: f64, samples_per_channel: u64) !void {
|
||||||
try checkDAQmxError(
|
const api = self.ni_daq.api;
|
||||||
c.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_FiniteSamps, samples_per_channel),
|
try self.ni_daq.checkDAQmxError(
|
||||||
|
api.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_FiniteSamps, samples_per_channel),
|
||||||
error.DAQmxCfgSampClkTiming
|
error.DAQmxCfgSampClkTiming
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -107,8 +113,9 @@ pub const Task = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn createAIVoltageChannel(self: Task, options: AIVoltageChannelOptions) !void {
|
pub fn createAIVoltageChannel(self: Task, options: AIVoltageChannelOptions) !void {
|
||||||
try checkDAQmxError(
|
const api = self.ni_daq.api;
|
||||||
c.DAQmxCreateAIVoltageChan(
|
try self.ni_daq.checkDAQmxError(
|
||||||
|
api.DAQmxCreateAIVoltageChan(
|
||||||
self.handle,
|
self.handle,
|
||||||
options.channel,
|
options.channel,
|
||||||
options.assigned_name,
|
options.assigned_name,
|
||||||
@ -132,7 +139,7 @@ pub const Task = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn createAOVoltageChannel(self: Task, options: AOVoltageChannelOptions) !void {
|
pub fn createAOVoltageChannel(self: Task, options: AOVoltageChannelOptions) !void {
|
||||||
try checkDAQmxError(
|
try self.ni_daq.checkDAQmxError(
|
||||||
c.DAQmxCreateAOVoltageChan(
|
c.DAQmxCreateAOVoltageChan(
|
||||||
self.handle,
|
self.handle,
|
||||||
options.channel,
|
options.channel,
|
||||||
@ -155,8 +162,9 @@ pub const Task = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn readAnalog(self: *Task, options: ReadAnalogOptions) !u32 {
|
pub fn readAnalog(self: *Task, options: ReadAnalogOptions) !u32 {
|
||||||
|
const api = self.ni_daq.api;
|
||||||
var samples_per_channel: i32 = 0;
|
var samples_per_channel: i32 = 0;
|
||||||
const err = c.DAQmxReadAnalogF64(
|
const err = api.DAQmxReadAnalogF64(
|
||||||
self.handle,
|
self.handle,
|
||||||
options.samples_per_channel,
|
options.samples_per_channel,
|
||||||
options.timeout,
|
options.timeout,
|
||||||
@ -171,7 +179,7 @@ pub const Task = struct {
|
|||||||
self.dropped_samples += 1;
|
self.dropped_samples += 1;
|
||||||
log.err("Dropped samples, not reading samples fast enough.", .{});
|
log.err("Dropped samples, not reading samples fast enough.", .{});
|
||||||
} else if (err < 0) {
|
} else if (err < 0) {
|
||||||
try checkDAQmxError(err, error.DAQmxReadAnalogF64);
|
try self.ni_daq.checkDAQmxError(err, error.DAQmxReadAnalogF64);
|
||||||
}
|
}
|
||||||
|
|
||||||
return @intCast(samples_per_channel);
|
return @intCast(samples_per_channel);
|
||||||
@ -179,7 +187,7 @@ pub const Task = struct {
|
|||||||
|
|
||||||
pub fn isDone(self: Task) !bool {
|
pub fn isDone(self: Task) !bool {
|
||||||
var result: c.bool32 = 0;
|
var result: c.bool32 = 0;
|
||||||
try checkDAQmxError(
|
try self.ni_daq.checkDAQmxError(
|
||||||
c.DAQmxIsTaskDone(self.handle, &result),
|
c.DAQmxIsTaskDone(self.handle, &result),
|
||||||
error.DAQmxIsTaskDone
|
error.DAQmxIsTaskDone
|
||||||
);
|
);
|
||||||
@ -365,12 +373,13 @@ const DeviceBuffers = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
options: Options,
|
options: Options,
|
||||||
|
api: *Api,
|
||||||
device_names_buffer: []u8,
|
device_names_buffer: []u8,
|
||||||
device_names: StringArrayListUnmanaged,
|
device_names: StringArrayListUnmanaged,
|
||||||
|
|
||||||
device_buffers: []DeviceBuffers,
|
device_buffers: []DeviceBuffers,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, options: Options) !NIDaq {
|
pub fn init(allocator: std.mem.Allocator, api: *Api, options: Options) !NIDaq {
|
||||||
const device_names_buffer_size = options.max_devices * (max_device_name_size + 2);
|
const device_names_buffer_size = options.max_devices * (max_device_name_size + 2);
|
||||||
const device_names_buffer = try allocator.alloc(u8, device_names_buffer_size);
|
const device_names_buffer = try allocator.alloc(u8, device_names_buffer_size);
|
||||||
errdefer allocator.free(device_names_buffer);
|
errdefer allocator.free(device_names_buffer);
|
||||||
@ -388,6 +397,7 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !NIDaq {
|
|||||||
|
|
||||||
return NIDaq{
|
return NIDaq{
|
||||||
.options = options,
|
.options = options,
|
||||||
|
.api = api,
|
||||||
.device_names_buffer = device_names_buffer,
|
.device_names_buffer = device_names_buffer,
|
||||||
.device_names = device_names,
|
.device_names = device_names,
|
||||||
.device_buffers = device_buffers
|
.device_buffers = device_buffers
|
||||||
@ -423,33 +433,33 @@ test {
|
|||||||
try std.testing.expectEqual(3, maxIndexStringLength(101));
|
try std.testing.expectEqual(3, maxIndexStringLength(101));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logDAQmxError(error_code: i32) void {
|
pub fn logDAQmxError(self: NIDaq, error_code: i32) void {
|
||||||
if (error_code == 0) {
|
if (error_code == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg: [512:0]u8 = .{ 0 } ** 512;
|
var msg: [512:0]u8 = .{ 0 } ** 512;
|
||||||
if (c.DAQmxGetErrorString(error_code, &msg, msg.len) == 0) {
|
if (self.api.DAQmxGetErrorString(error_code, &msg, msg.len) == 0) {
|
||||||
log.err("DAQmx ({}): {s}", .{error_code, msg});
|
log.err("DAQmx ({}): {s}", .{error_code, msg});
|
||||||
} else {
|
} else {
|
||||||
log.err("DAQmx ({}): Unknown (Buffer too small for message)", .{error_code});
|
log.err("DAQmx ({}): Unknown (Buffer too small for message)", .{error_code});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkDAQmxError(error_code: i32, err: anyerror) !void {
|
pub fn checkDAQmxError(self: NIDaq, error_code: i32, err: anyerror) !void {
|
||||||
logDAQmxError(error_code);
|
self.logDAQmxError(error_code);
|
||||||
|
|
||||||
if (error_code < 0) {
|
if (error_code < 0) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkDAQmxErrorIgnoreWarnings(error_code: i32, err: anyerror) !void {
|
pub fn checkDAQmxErrorIgnoreWarnings(self: NIDaq, error_code: i32, err: anyerror) !void {
|
||||||
if (error_code > 0) {
|
if (error_code > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try checkDAQmxError(error_code, err);
|
try self.checkDAQmxError(error_code, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), buffer: []u8) !void {
|
fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), buffer: []u8) !void {
|
||||||
@ -471,22 +481,22 @@ fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), bu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version() !std.SemanticVersion {
|
pub fn version(self: NIDaq) !std.SemanticVersion {
|
||||||
var major: u32 = 0;
|
var major: u32 = 0;
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetSysNIDAQMajorVersion(&major),
|
self.api.DAQmxGetSysNIDAQMajorVersion(&major),
|
||||||
error.GetMajorVersion
|
error.GetMajorVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
var minor: u32 = 0;
|
var minor: u32 = 0;
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetSysNIDAQMinorVersion(&minor),
|
self.api.DAQmxGetSysNIDAQMinorVersion(&minor),
|
||||||
error.GetMinorVersion
|
error.GetMinorVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
var update: u32 = 0;
|
var update: u32 = 0;
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetSysNIDAQUpdateVersion(&update),
|
self.api.DAQmxGetSysNIDAQUpdateVersion(&update),
|
||||||
error.GetUpdateVersion
|
error.GetUpdateVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -501,8 +511,8 @@ pub fn listDeviceNames(self: *NIDaq) ![]const [:0]const u8 {
|
|||||||
self.device_names.clearRetainingCapacity();
|
self.device_names.clearRetainingCapacity();
|
||||||
self.clearAllDeviceBuffers();
|
self.clearAllDeviceBuffers();
|
||||||
|
|
||||||
const required_size = c.DAQmxGetSysDevNames(null, 0);
|
const required_size = self.api.DAQmxGetSysDevNames(null, 0);
|
||||||
try checkDAQmxErrorIgnoreWarnings(required_size, error.DAQmxGetSysDevNames);
|
try self.checkDAQmxErrorIgnoreWarnings(required_size, error.DAQmxGetSysDevNames);
|
||||||
if (required_size == 0) {
|
if (required_size == 0) {
|
||||||
return self.device_names.items;
|
return self.device_names.items;
|
||||||
}
|
}
|
||||||
@ -511,8 +521,8 @@ pub fn listDeviceNames(self: *NIDaq) ![]const [:0]const u8 {
|
|||||||
return error.BufferTooSmall;
|
return error.BufferTooSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetSysDevNames(self.device_names_buffer.ptr, @intCast(self.device_names_buffer.len)),
|
self.api.DAQmxGetSysDevNames(self.device_names_buffer.ptr, @intCast(self.device_names_buffer.len)),
|
||||||
error.GetDeviceNames
|
error.GetDeviceNames
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -549,6 +559,7 @@ fn clearAllDeviceBuffers(self: *NIDaq) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn listDevicePhysicalChannels(
|
fn listDevicePhysicalChannels(
|
||||||
|
self: NIDaq,
|
||||||
getPhysicalChannels: anytype,
|
getPhysicalChannels: anytype,
|
||||||
device: [:0]const u8,
|
device: [:0]const u8,
|
||||||
channel_names: *DeviceBuffers.ChannelNames,
|
channel_names: *DeviceBuffers.ChannelNames,
|
||||||
@ -559,7 +570,7 @@ fn listDevicePhysicalChannels(
|
|||||||
array_list.clearRetainingCapacity();
|
array_list.clearRetainingCapacity();
|
||||||
|
|
||||||
const required_size = getPhysicalChannels(device, null, 0);
|
const required_size = getPhysicalChannels(device, null, 0);
|
||||||
try checkDAQmxErrorIgnoreWarnings(required_size, error.GetPhysicalChannels);
|
try self.checkDAQmxErrorIgnoreWarnings(required_size, error.GetPhysicalChannels);
|
||||||
if (required_size == 0) {
|
if (required_size == 0) {
|
||||||
return array_list.items;
|
return array_list.items;
|
||||||
}
|
}
|
||||||
@ -568,7 +579,7 @@ fn listDevicePhysicalChannels(
|
|||||||
return error.BufferTooSmall;
|
return error.BufferTooSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
getPhysicalChannels(device, buffer.ptr, @intCast(buffer.len)),
|
getPhysicalChannels(device, buffer.ptr, @intCast(buffer.len)),
|
||||||
error.GetPhysicalChannels
|
error.GetPhysicalChannels
|
||||||
);
|
);
|
||||||
@ -584,8 +595,8 @@ fn listDevicePhysicalChannels(
|
|||||||
pub fn listDeviceAIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
pub fn listDeviceAIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
||||||
var device_buffers = try self.getDeviceBuffers(device);
|
var device_buffers = try self.getDeviceBuffers(device);
|
||||||
|
|
||||||
return listDevicePhysicalChannels(
|
return self.listDevicePhysicalChannels(
|
||||||
c.DAQmxGetDevAIPhysicalChans,
|
self.api.DAQmxGetDevAIPhysicalChans,
|
||||||
device,
|
device,
|
||||||
&device_buffers.analog_input_names
|
&device_buffers.analog_input_names
|
||||||
);
|
);
|
||||||
@ -594,8 +605,8 @@ pub fn listDeviceAIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const
|
|||||||
pub fn listDeviceAOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
pub fn listDeviceAOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
||||||
var device_buffers = try self.getDeviceBuffers(device);
|
var device_buffers = try self.getDeviceBuffers(device);
|
||||||
|
|
||||||
return listDevicePhysicalChannels(
|
return self.listDevicePhysicalChannels(
|
||||||
c.DAQmxGetDevAOPhysicalChans,
|
self.api.DAQmxGetDevAOPhysicalChans,
|
||||||
device,
|
device,
|
||||||
&device_buffers.analog_output_names
|
&device_buffers.analog_output_names
|
||||||
);
|
);
|
||||||
@ -604,7 +615,7 @@ pub fn listDeviceAOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const
|
|||||||
pub fn listDeviceCOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
pub fn listDeviceCOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
||||||
var device_buffers = try self.getDeviceBuffers(device);
|
var device_buffers = try self.getDeviceBuffers(device);
|
||||||
|
|
||||||
return listDevicePhysicalChannels(
|
return self.listDevicePhysicalChannels(
|
||||||
c.DAQmxGetDevCOPhysicalChans,
|
c.DAQmxGetDevCOPhysicalChans,
|
||||||
device,
|
device,
|
||||||
&device_buffers.counter_output_names
|
&device_buffers.counter_output_names
|
||||||
@ -614,7 +625,7 @@ pub fn listDeviceCOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const
|
|||||||
pub fn listDeviceCIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
pub fn listDeviceCIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
|
||||||
var device_buffers = try self.getDeviceBuffers(device);
|
var device_buffers = try self.getDeviceBuffers(device);
|
||||||
|
|
||||||
return listDevicePhysicalChannels(
|
return self.listDevicePhysicalChannels(
|
||||||
c.DAQmxGetDevCIPhysicalChans,
|
c.DAQmxGetDevCIPhysicalChans,
|
||||||
device,
|
device,
|
||||||
&device_buffers.counter_input_names
|
&device_buffers.counter_input_names
|
||||||
@ -646,21 +657,19 @@ fn getChannelType(device: [:0]const u8) ?ChannelType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getMinSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
|
pub fn getMinSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
|
||||||
_ = self;
|
|
||||||
|
|
||||||
var result: f64 = 0;
|
var result: f64 = 0;
|
||||||
|
|
||||||
const channel_type = getChannelType(channel) orelse return error.UnknownChannelType;
|
const channel_type = getChannelType(channel) orelse return error.UnknownChannelType;
|
||||||
switch (channel_type) {
|
switch (channel_type) {
|
||||||
.analog_input => {
|
.analog_input => {
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetDevAIMinRate(channel, &result),
|
self.api.DAQmxGetDevAIMinRate(channel, &result),
|
||||||
error.DAQmxGetDevAIMinRate
|
error.DAQmxGetDevAIMinRate
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
.analog_output => {
|
.analog_output => {
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetDevAOMinRate(channel, &result),
|
self.api.DAQmxGetDevAOMinRate(channel, &result),
|
||||||
error.DAQmxGetDevAOMinRate
|
error.DAQmxGetDevAOMinRate
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -674,21 +683,19 @@ pub fn getMinSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getMaxSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
|
pub fn getMaxSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
|
||||||
_ = self;
|
|
||||||
|
|
||||||
var result: f64 = 0;
|
var result: f64 = 0;
|
||||||
|
|
||||||
const channel_type = getChannelType(channel) orelse return error.UnknownChannelType;
|
const channel_type = getChannelType(channel) orelse return error.UnknownChannelType;
|
||||||
switch (channel_type) {
|
switch (channel_type) {
|
||||||
.analog_input => {
|
.analog_input => {
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetDevAIMaxSingleChanRate(channel, &result),
|
self.api.DAQmxGetDevAIMaxSingleChanRate(channel, &result),
|
||||||
error.DAQmxGetDevAIMaxSingleChanRate
|
error.DAQmxGetDevAIMaxSingleChanRate
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
.analog_output => {
|
.analog_output => {
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetDevAOMaxRate(channel, &result),
|
self.api.DAQmxGetDevAOMaxRate(channel, &result),
|
||||||
error.DAQmxGetDevAOMaxRate
|
error.DAQmxGetDevAOMaxRate
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -702,6 +709,7 @@ pub fn getMaxSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn listDeviceVoltageRanges(
|
fn listDeviceVoltageRanges(
|
||||||
|
self: NIDaq,
|
||||||
getVoltageRanges: anytype,
|
getVoltageRanges: anytype,
|
||||||
device: [:0]const u8,
|
device: [:0]const u8,
|
||||||
voltage_ranges: *std.ArrayListUnmanaged(Range)
|
voltage_ranges: *std.ArrayListUnmanaged(Range)
|
||||||
@ -709,7 +717,7 @@ fn listDeviceVoltageRanges(
|
|||||||
voltage_ranges.clearRetainingCapacity();
|
voltage_ranges.clearRetainingCapacity();
|
||||||
|
|
||||||
const count = getVoltageRanges(device, null, 0);
|
const count = getVoltageRanges(device, null, 0);
|
||||||
try checkDAQmxErrorIgnoreWarnings(count, error.GetVoltageRanges);
|
try self.checkDAQmxErrorIgnoreWarnings(count, error.GetVoltageRanges);
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
return voltage_ranges.items;
|
return voltage_ranges.items;
|
||||||
}
|
}
|
||||||
@ -722,7 +730,7 @@ fn listDeviceVoltageRanges(
|
|||||||
return error.BufferTooSmall;
|
return error.BufferTooSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
getVoltageRanges(device, buffer, @intCast(buffer_len)),
|
getVoltageRanges(device, buffer, @intCast(buffer_len)),
|
||||||
error.GetVoltageRanges
|
error.GetVoltageRanges
|
||||||
);
|
);
|
||||||
@ -735,8 +743,8 @@ fn listDeviceVoltageRanges(
|
|||||||
pub fn listDeviceAIVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
|
pub fn listDeviceAIVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
|
||||||
var device_buffers = try self.getDeviceBuffers(device);
|
var device_buffers = try self.getDeviceBuffers(device);
|
||||||
|
|
||||||
return listDeviceVoltageRanges(
|
return self.listDeviceVoltageRanges(
|
||||||
c.DAQmxGetDevAIVoltageRngs,
|
self.api.DAQmxGetDevAIVoltageRngs,
|
||||||
device,
|
device,
|
||||||
&device_buffers.analog_input_voltage_ranges
|
&device_buffers.analog_input_voltage_ranges
|
||||||
);
|
);
|
||||||
@ -745,36 +753,33 @@ pub fn listDeviceAIVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
|
|||||||
pub fn listDeviceAOVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
|
pub fn listDeviceAOVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
|
||||||
var device_buffers = try self.getDeviceBuffers(device);
|
var device_buffers = try self.getDeviceBuffers(device);
|
||||||
|
|
||||||
return listDeviceVoltageRanges(
|
return self.listDeviceVoltageRanges(
|
||||||
c.DAQmxGetDevAOVoltageRngs,
|
self.api.DAQmxGetDevAOVoltageRngs,
|
||||||
device,
|
device,
|
||||||
&device_buffers.analog_output_voltage_ranges
|
&device_buffers.analog_output_voltage_ranges
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createTask(self: NIDaq, name: ?[:0]const u8) !Task {
|
pub fn createTask(self: *NIDaq, name: ?[:0]const u8) !Task {
|
||||||
_ = self;
|
|
||||||
|
|
||||||
var handle: TaskHandle = undefined;
|
var handle: TaskHandle = undefined;
|
||||||
|
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxCreateTask(name orelse "", &handle),
|
self.api.DAQmxCreateTask(name orelse "", &handle),
|
||||||
error.DAQmxCreateTask
|
error.DAQmxCreateTask
|
||||||
);
|
);
|
||||||
|
|
||||||
return Task{ .handle = handle };
|
return Task{ .ni_daq = self, .handle = handle };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listDeviceAIMeasurementTypes(self: NIDaq, device: [:0]const u8) !AIMeasurementTypeList {
|
pub fn listDeviceAIMeasurementTypes(self: NIDaq, device: [:0]const u8) !AIMeasurementTypeList {
|
||||||
var result = AIMeasurementTypeList.init(0) catch unreachable;
|
var result = AIMeasurementTypeList.init(0) catch unreachable;
|
||||||
_ = self;
|
|
||||||
|
|
||||||
const count = c.DAQmxGetDevAISupportedMeasTypes(device, null, 0);
|
const count = self.api.DAQmxGetDevAISupportedMeasTypes(device, null, 0);
|
||||||
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAISupportedMeasTypes);
|
try self.checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAISupportedMeasTypes);
|
||||||
assert(count <= result.buffer.len);
|
assert(count <= result.buffer.len);
|
||||||
|
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetDevAISupportedMeasTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
|
self.api.DAQmxGetDevAISupportedMeasTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
|
||||||
error.DAQmxGetDevAISupportedMeasTypes
|
error.DAQmxGetDevAISupportedMeasTypes
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -790,14 +795,13 @@ pub fn checkDeviceAIMeasurementType(self: NIDaq, device: [:0]const u8, measureme
|
|||||||
|
|
||||||
pub fn listDeviceAOOutputTypes(self: NIDaq, device: [:0]const u8) !AOOutputTypeList {
|
pub fn listDeviceAOOutputTypes(self: NIDaq, device: [:0]const u8) !AOOutputTypeList {
|
||||||
var result = AOOutputTypeList.init(0) catch unreachable;
|
var result = AOOutputTypeList.init(0) catch unreachable;
|
||||||
_ = self;
|
|
||||||
|
|
||||||
const count = c.DAQmxGetDevAOSupportedOutputTypes(device, null, 0);
|
const count = self.api.DAQmxGetDevAOSupportedOutputTypes(device, null, 0);
|
||||||
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAOSupportedOutputTypes);
|
try self.checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAOSupportedOutputTypes);
|
||||||
assert(count <= result.buffer.len);
|
assert(count <= result.buffer.len);
|
||||||
|
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetDevAOSupportedOutputTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
|
self.api.DAQmxGetDevAOSupportedOutputTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
|
||||||
error.DAQmxGetDevAOSupportedOutputTypes
|
error.DAQmxGetDevAOSupportedOutputTypes
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -812,11 +816,9 @@ pub fn checkDeviceAOOutputType(self: NIDaq, device: [:0]const u8, output_type: A
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDeviceProductCategory(self: NIDaq, device: [:0]const u8) !ProductCategory {
|
pub fn getDeviceProductCategory(self: NIDaq, device: [:0]const u8) !ProductCategory {
|
||||||
_ = self;
|
|
||||||
|
|
||||||
var product_category = ProductCategory.Unknown;
|
var product_category = ProductCategory.Unknown;
|
||||||
try checkDAQmxError(
|
try self.checkDAQmxError(
|
||||||
c.DAQmxGetDevProductCategory(device, @ptrCast(&product_category)),
|
self.api.DAQmxGetDevProductCategory(device, @ptrCast(&product_category)),
|
||||||
error.DAQmxGetDevProductCategory
|
error.DAQmxGetDevProductCategory
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const NIDaq = @import("./ni-daq.zig");
|
const NIDaq = @import("./root.zig");
|
||||||
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const log = std.log.scoped(.task_pool);
|
const log = std.log.scoped(.task_pool);
|
||||||
@ -43,12 +43,10 @@ pub const Entry = struct {
|
|||||||
|
|
||||||
running: bool = false,
|
running: bool = false,
|
||||||
read_thread: std.Thread,
|
read_thread: std.Thread,
|
||||||
ni_daq: *NIDaq,
|
|
||||||
entries: [max_tasks]Entry = undefined,
|
entries: [max_tasks]Entry = undefined,
|
||||||
|
|
||||||
pub fn init(self: *TaskPool, allocator: std.mem.Allocator, ni_daq: *NIDaq) !void {
|
pub fn init(self: *TaskPool, allocator: std.mem.Allocator) !void {
|
||||||
self.* = TaskPool{
|
self.* = TaskPool{
|
||||||
.ni_daq = ni_daq,
|
|
||||||
.read_thread = undefined
|
.read_thread = undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,12 +130,13 @@ fn findFreeEntry(self: *TaskPool) ?*Entry {
|
|||||||
|
|
||||||
pub fn launchAIVoltageChannel(
|
pub fn launchAIVoltageChannel(
|
||||||
self: *TaskPool,
|
self: *TaskPool,
|
||||||
|
ni_daq: *NIDaq,
|
||||||
mutex: *std.Thread.Mutex,
|
mutex: *std.Thread.Mutex,
|
||||||
samples: *std.ArrayList(f64),
|
samples: *std.ArrayList(f64),
|
||||||
sampling: Sampling,
|
sampling: Sampling,
|
||||||
options: NIDaq.Task.AIVoltageChannelOptions
|
options: NIDaq.Task.AIVoltageChannelOptions
|
||||||
) !*Entry {
|
) !*Entry {
|
||||||
const task = try self.ni_daq.createTask(null);
|
const task = try ni_daq.createTask(null);
|
||||||
errdefer task.clear();
|
errdefer task.clear();
|
||||||
|
|
||||||
const entry = self.findFreeEntry() orelse return error.NotEnoughSpace;
|
const entry = self.findFreeEntry() orelse return error.NotEnoughSpace;
|
||||||
@ -133,3 +133,9 @@ pub fn openFilePicker() !std.fs.File {
|
|||||||
// Could not get it to work, because it always threw OBJECT_PATH_SYNTAX_BAD error
|
// Could not get it to work, because it always threw OBJECT_PATH_SYNTAX_BAD error
|
||||||
return try std.fs.openFileAbsolute(filename, .{ });
|
return try std.fs.openFileAbsolute(filename, .{ });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init() void {
|
||||||
|
if (builtin.os.tag == .windows) {
|
||||||
|
_ = windows_h.SetConsoleOutputCP(65001);
|
||||||
|
}
|
||||||
|
}
|
||||||
428
src/ui.zig
428
src/ui.zig
@ -3,6 +3,7 @@ const rl = @import("raylib");
|
|||||||
const Assets = @import("./assets.zig");
|
const Assets = @import("./assets.zig");
|
||||||
const rect_utils = @import("./rect-utils.zig");
|
const rect_utils = @import("./rect-utils.zig");
|
||||||
const srcery = @import("./srcery.zig");
|
const srcery = @import("./srcery.zig");
|
||||||
|
const FontFace = @import("./font-face.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.ui);
|
const log = std.log.scoped(.ui);
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
@ -242,6 +243,9 @@ pub const Box = struct {
|
|||||||
pub const Flag = enum {
|
pub const Flag = enum {
|
||||||
clickable,
|
clickable,
|
||||||
|
|
||||||
|
highlight_hot,
|
||||||
|
highlight_active,
|
||||||
|
|
||||||
draggable_x,
|
draggable_x,
|
||||||
draggable_y,
|
draggable_y,
|
||||||
|
|
||||||
@ -250,7 +254,15 @@ pub const Box = struct {
|
|||||||
fixed_x,
|
fixed_x,
|
||||||
fixed_y,
|
fixed_y,
|
||||||
fixed_width,
|
fixed_width,
|
||||||
fixed_height
|
fixed_height,
|
||||||
|
|
||||||
|
hover_mouse_hand,
|
||||||
|
|
||||||
|
skip_draw,
|
||||||
|
|
||||||
|
text_underline,
|
||||||
|
text_wrapping,
|
||||||
|
text_left_align
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Flags = std.EnumSet(Flag);
|
pub const Flags = std.EnumSet(Flag);
|
||||||
@ -305,7 +317,19 @@ pub const Box = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAllocText(self: *Box, font: Assets.FontId, comptime fmt: []const u8, args: anytype) void {
|
pub fn appendText(self: *Box, text: []const u8) void {
|
||||||
|
if (self.text == null) {
|
||||||
|
self.text = .{
|
||||||
|
.content = self.allocator.alloc(u8, 0) catch return,
|
||||||
|
.font = .text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const new_content = std.mem.concat(self.allocator, u8, &.{ self.text.?.content, text }) catch return;
|
||||||
|
self.text.?.content = new_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setFmtText(self: *Box, font: Assets.FontId, comptime fmt: []const u8, args: anytype) void {
|
||||||
self.text = .{
|
self.text = .{
|
||||||
.content = std.fmt.allocPrint(self.allocator, fmt, args) catch return,
|
.content = std.fmt.allocPrint(self.allocator, fmt, args) catch return,
|
||||||
.font = font
|
.font = font
|
||||||
@ -543,7 +567,7 @@ pub fn end(self: *UI) void {
|
|||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ew));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ew));
|
||||||
} else if (active_box_flags.contains(.draggable_y)) {
|
} else if (active_box_flags.contains(.draggable_y)) {
|
||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ns));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ns));
|
||||||
} else if (hover_box_flags.contains(.clickable)) {
|
} else if (hover_box_flags.contains(.hover_mouse_hand)) {
|
||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_pointing_hand));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_pointing_hand));
|
||||||
} else {
|
} else {
|
||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_default));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_default));
|
||||||
@ -645,6 +669,10 @@ pub fn draw(self: *UI) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn drawBox(self: *UI, box: *Box) void {
|
fn drawBox(self: *UI, box: *Box) void {
|
||||||
|
if (box.flags.contains(.skip_draw)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const box_rect = box.computedRect();
|
const box_rect = box.computedRect();
|
||||||
|
|
||||||
const do_scissor = box.hasClipping();
|
const do_scissor = box.hasClipping();
|
||||||
@ -662,11 +690,15 @@ fn drawBox(self: *UI, box: *Box) void {
|
|||||||
rl.drawRectangleRec(box_rect, background);
|
rl.drawRectangleRec(box_rect, background);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.isBoxActive(box.key)) {
|
if (self.isKeyActive(box.key)) {
|
||||||
|
if (box.flags.contains(.highlight_active)) {
|
||||||
rl.drawRectangleLinesEx(box_rect, 2, rl.Color.orange);
|
rl.drawRectangleLinesEx(box_rect, 2, rl.Color.orange);
|
||||||
} else if (self.isBoxHot(box.key)) {
|
}
|
||||||
|
} else if (self.isKeyHot(box.key)) {
|
||||||
|
if (box.flags.contains(.highlight_hot)) {
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.blue);
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.blue);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (box.texture) |texture| {
|
if (box.texture) |texture| {
|
||||||
const source = rl.Rectangle{
|
const source = rl.Rectangle{
|
||||||
@ -686,7 +718,38 @@ fn drawBox(self: *UI, box: *Box) void {
|
|||||||
|
|
||||||
if (box.text) |text| {
|
if (box.text) |text| {
|
||||||
const font = Assets.font(text.font);
|
const font = Assets.font(text.font);
|
||||||
font.drawTextCenter(text.content, rect_utils.center(box_rect), text.color);
|
const text_size = font.measureText(text.content);
|
||||||
|
var text_rect = Rect{
|
||||||
|
.x = box_rect.x,
|
||||||
|
.y = box_rect.y,
|
||||||
|
.width = text_size.x,
|
||||||
|
.height = text_size.y
|
||||||
|
};
|
||||||
|
|
||||||
|
if (box.flags.contains(.text_left_align)) {
|
||||||
|
if (box.size.x.kind == .text) {
|
||||||
|
const padding = box.size.x.kind.text;
|
||||||
|
text_rect.x += padding * font.getSize() / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.size.y.kind == .text) {
|
||||||
|
const padding = box.size.y.kind.text;
|
||||||
|
text_rect.y += padding * font.getSize() / 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text_rect.x += (box_rect.width - text_size.x) / 2;
|
||||||
|
text_rect.y += (box_rect.height - text_size.y) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
font.drawText(text.content, .{ .x = text_rect.x, .y = text_rect.y }, text.color);
|
||||||
|
|
||||||
|
if (box.flags.contains(.text_underline)) {
|
||||||
|
rl.drawLineV(
|
||||||
|
rect_utils.bottomLeft(text_rect),
|
||||||
|
rect_utils.bottomRight(text_rect),
|
||||||
|
text.color
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
@ -695,9 +758,9 @@ fn drawBox(self: *UI, box: *Box) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
if (self.isBoxActive(box.key)) {
|
if (self.isKeyActive(box.key)) {
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
|
||||||
} else if (self.isBoxHot(box.key)) {
|
} else if (self.isKeyHot(box.key)) {
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.orange);
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.orange);
|
||||||
} else {
|
} else {
|
||||||
rl.drawRectangleLinesEx(box_rect, 1, rl.Color.pink);
|
rl.drawRectangleLinesEx(box_rect, 1, rl.Color.pink);
|
||||||
@ -790,6 +853,8 @@ fn calcLayoutDownardsSize(self: *UI, box: *Box, axis: Axis) void {
|
|||||||
const computed_size = getVec2Axis(&box.persistent.size, axis);
|
const computed_size = getVec2Axis(&box.persistent.size, axis);
|
||||||
|
|
||||||
if (size.kind == .children_sum) {
|
if (size.kind == .children_sum) {
|
||||||
|
var first_child: bool = true;
|
||||||
|
|
||||||
var sum: f32 = 0;
|
var sum: f32 = 0;
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
while (child_iter.next()) |child| {
|
while (child_iter.next()) |child| {
|
||||||
@ -799,83 +864,36 @@ fn calcLayoutDownardsSize(self: *UI, box: *Box, axis: Axis) void {
|
|||||||
|
|
||||||
if (box.layout_axis == axis) {
|
if (box.layout_axis == axis) {
|
||||||
sum += child_size;
|
sum += child_size;
|
||||||
|
if (!first_child) {
|
||||||
|
sum += box.layout_gap;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sum = @max(sum, child_size);
|
sum = @max(sum, child_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
first_child = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
computed_size.* = sum;
|
computed_size.* = sum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calcLayoutPositions(self: *UI, box: *Box, axis: Axis) void {
|
|
||||||
{
|
|
||||||
var layout_position: f32 = 0;
|
|
||||||
|
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
|
||||||
while (child_iter.next()) |child| {
|
|
||||||
const child_axis_position = getVec2Axis(&child.persistent.position, axis);
|
|
||||||
|
|
||||||
if (child.getFixedPositionAxis(axis)) |position| {
|
|
||||||
child_axis_position.* = position;
|
|
||||||
} else {
|
|
||||||
const child_axis_size = getVec2Axis(&child.persistent.size, axis);
|
|
||||||
const parent_axis_position = getVec2Axis(&box.persistent.position, axis);
|
|
||||||
|
|
||||||
child_axis_position.* = parent_axis_position.*;
|
|
||||||
|
|
||||||
if (box.layout_axis == axis) {
|
|
||||||
child_axis_position.* += layout_position;
|
|
||||||
layout_position += child_axis_size.*;
|
|
||||||
layout_position += box.layout_gap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
child_axis_position.* -= getVec2Axis(&box.view_offset, axis).*;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (box.layout_axis == axis) {
|
|
||||||
var child_size_sum: f32 = 0;
|
|
||||||
var child_count: f32 = 0;
|
|
||||||
|
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
|
||||||
while (child_iter.next()) |child| {
|
|
||||||
if (child.isPositionFixed(axis)) continue;
|
|
||||||
|
|
||||||
const child_size = getVec2Axis(&child.persistent.size, axis);
|
|
||||||
child_size_sum += child_size.*;
|
|
||||||
child_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child_count > 1) {
|
|
||||||
child_size_sum += (child_count - 1) * box.layout_gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
getVec2Axis(&box.persistent.children_size, axis).* = child_size_sum;
|
|
||||||
} else {
|
|
||||||
var max_child_size: f32 = 0;
|
|
||||||
|
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
|
||||||
while (child_iter.next()) |child| {
|
|
||||||
if (child.isPositionFixed(axis)) continue;
|
|
||||||
|
|
||||||
const child_size = getVec2Axis(&child.persistent.size, axis);
|
|
||||||
max_child_size = @max(max_child_size, child_size.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
getVec2Axis(&box.persistent.children_size, axis).* = max_child_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
|
||||||
while (child_iter.next()) |child| {
|
|
||||||
self.calcLayoutPositions(child, axis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
|
fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
|
||||||
|
if (box.text != null and box.flags.contains(.text_wrapping)) {
|
||||||
|
const text = box.text.?;
|
||||||
|
const font = Assets.font(text.font);
|
||||||
|
const axis_size = box.size.getAxis(axis);
|
||||||
|
|
||||||
|
var max_width = box.persistent.size.x;
|
||||||
|
if (axis_size.kind == .text) {
|
||||||
|
max_width -= axis_size.kind.text * font.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrapText(box.allocator, font, text.content, max_width) catch null) |wrapped_content| {
|
||||||
|
box.text.?.content = wrapped_content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Children can't be wider than the parent on the secondary axis
|
// Children can't be wider than the parent on the secondary axis
|
||||||
if (box.layout_axis != axis) {
|
if (box.layout_axis != axis) {
|
||||||
const max_child_size = getVec2Axis(&box.persistent.size, axis).*;
|
const max_child_size = getVec2Axis(&box.persistent.size, axis).*;
|
||||||
@ -949,12 +967,134 @@ fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newKeyFromString(self: *UI, text: []const u8) Key {
|
fn calcLayoutPositions(self: *UI, box: *Box, axis: Axis) void {
|
||||||
var parent_hash: u64 = 0;
|
{
|
||||||
if (self.getParent()) |parent| {
|
var layout_position: f32 = 0;
|
||||||
parent_hash = parent.key.hash;
|
|
||||||
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
|
while (child_iter.next()) |child| {
|
||||||
|
const child_axis_position = getVec2Axis(&child.persistent.position, axis);
|
||||||
|
|
||||||
|
if (child.getFixedPositionAxis(axis)) |position| {
|
||||||
|
child_axis_position.* = position;
|
||||||
|
} else {
|
||||||
|
const child_axis_size = getVec2Axis(&child.persistent.size, axis);
|
||||||
|
const parent_axis_position = getVec2Axis(&box.persistent.position, axis);
|
||||||
|
|
||||||
|
child_axis_position.* = parent_axis_position.*;
|
||||||
|
|
||||||
|
if (box.layout_axis == axis) {
|
||||||
|
child_axis_position.* += layout_position;
|
||||||
|
layout_position += child_axis_size.*;
|
||||||
|
layout_position += box.layout_gap;
|
||||||
}
|
}
|
||||||
return Key.initString(parent_hash, text);
|
}
|
||||||
|
|
||||||
|
child_axis_position.* -= getVec2Axis(&box.view_offset, axis).*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.layout_axis == axis) {
|
||||||
|
var child_size_sum: f32 = 0;
|
||||||
|
var child_count: f32 = 0;
|
||||||
|
|
||||||
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
|
while (child_iter.next()) |child| {
|
||||||
|
if (child.isPositionFixed(axis)) continue;
|
||||||
|
|
||||||
|
const child_size = getVec2Axis(&child.persistent.size, axis);
|
||||||
|
child_size_sum += child_size.*;
|
||||||
|
child_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child_count > 1) {
|
||||||
|
child_size_sum += (child_count - 1) * box.layout_gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
getVec2Axis(&box.persistent.children_size, axis).* = child_size_sum;
|
||||||
|
} else {
|
||||||
|
var max_child_size: f32 = 0;
|
||||||
|
|
||||||
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
|
while (child_iter.next()) |child| {
|
||||||
|
if (child.isPositionFixed(axis)) continue;
|
||||||
|
|
||||||
|
const child_size = getVec2Axis(&child.persistent.size, axis);
|
||||||
|
max_child_size = @max(max_child_size, child_size.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
getVec2Axis(&box.persistent.children_size, axis).* = max_child_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
|
while (child_iter.next()) |child| {
|
||||||
|
self.calcLayoutPositions(child, axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrapText(allocator: std.mem.Allocator, font: FontFace, text: []const u8, max_width: f32) !?[]u8 {
|
||||||
|
// TODO: Implement word wrapping that doesn't include re-allocating the whole text
|
||||||
|
if (font.measureWidth(text) < max_width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = try std.ArrayList(u8).initCapacity(allocator, text.len);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
var line_buffer = std.ArrayList(u8).init(allocator);
|
||||||
|
defer line_buffer.deinit();
|
||||||
|
|
||||||
|
var word_iter = std.mem.splitScalar(u8, text, ' ');
|
||||||
|
while (word_iter.next()) |word| {
|
||||||
|
try line_buffer.ensureUnusedCapacity(word.len + 1);
|
||||||
|
|
||||||
|
const current_line = line_buffer.items;
|
||||||
|
if (line_buffer.items.len > 0) {
|
||||||
|
try line_buffer.append(' ');
|
||||||
|
}
|
||||||
|
try line_buffer.appendSlice(word);
|
||||||
|
|
||||||
|
if (font.measureWidth(line_buffer.items) > max_width) {
|
||||||
|
if (result.items.len > 0) {
|
||||||
|
try result.append('\n');
|
||||||
|
}
|
||||||
|
try result.appendSlice(current_line);
|
||||||
|
|
||||||
|
line_buffer.clearRetainingCapacity();
|
||||||
|
try line_buffer.appendSlice(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line_buffer.items.len > 0) {
|
||||||
|
if (result.items.len > 0) {
|
||||||
|
try result.append('\n');
|
||||||
|
}
|
||||||
|
try result.appendSlice(line_buffer.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return try result.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getKeySeed(self: *UI) u64 {
|
||||||
|
var maybe_current = self.getParent();
|
||||||
|
while (maybe_current) |current| {
|
||||||
|
if (!current.key.isNil()) {
|
||||||
|
return current.key.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_current = null;
|
||||||
|
if (current.parent_index) |parent_index| {
|
||||||
|
maybe_current = &self.boxes.buffer[parent_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newKeyFromString(self: *UI, text: []const u8) Key {
|
||||||
|
return Key.initString(self.getKeySeed(), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newBoxFromString(self: *UI, text: []const u8) *Box {
|
pub fn newBoxFromString(self: *UI, text: []const u8) *Box {
|
||||||
@ -965,7 +1105,26 @@ pub fn newBoxFromPtr(self: *UI, ptr: anytype) *Box {
|
|||||||
return self.newBox(Key.initPtr(ptr));
|
return self.newBox(Key.initPtr(ptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newBox(self: *UI, key: Key) *Box {
|
pub fn appendBox(self: *UI, box: *Box) void {
|
||||||
|
assert(box.parent_index == null);
|
||||||
|
|
||||||
|
if (self.getParentIndex()) |parent_index| {
|
||||||
|
const parent = &self.boxes.buffer[parent_index];
|
||||||
|
box.parent_index = parent_index;
|
||||||
|
|
||||||
|
if (parent.last_child_index) |last_child_index| {
|
||||||
|
const last_child = &self.boxes.buffer[last_child_index];
|
||||||
|
|
||||||
|
last_child.next_sibling_index = box.index;
|
||||||
|
parent.last_child_index = box.index;
|
||||||
|
} else {
|
||||||
|
parent.first_child_index = box.index;
|
||||||
|
parent.last_child_index = box.index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newBoxNoAppend(self: *UI, key: Key) *Box {
|
||||||
var box: *Box = undefined;
|
var box: *Box = undefined;
|
||||||
var box_index: ?BoxIndex = null;
|
var box_index: ?BoxIndex = null;
|
||||||
var persistent: Box.Persistent = .{};
|
var persistent: Box.Persistent = .{};
|
||||||
@ -995,21 +1154,12 @@ pub fn newBox(self: *UI, key: Key) *Box {
|
|||||||
.index = box_index.?
|
.index = box_index.?
|
||||||
};
|
};
|
||||||
|
|
||||||
if (self.getParentIndex()) |parent_index| {
|
return box;
|
||||||
const parent = &self.boxes.buffer[parent_index];
|
}
|
||||||
box.parent_index = parent_index;
|
|
||||||
|
|
||||||
if (parent.last_child_index) |last_child_index| {
|
|
||||||
const last_child = &self.boxes.buffer[last_child_index];
|
|
||||||
|
|
||||||
last_child.next_sibling_index = box.index;
|
|
||||||
parent.last_child_index = box.index;
|
|
||||||
} else {
|
|
||||||
parent.first_child_index = box.index;
|
|
||||||
parent.last_child_index = box.index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn newBox(self: *UI, key: Key) *Box {
|
||||||
|
const box = self.newBoxNoAppend(key);
|
||||||
|
self.appendBox(box);
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1056,7 +1206,7 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal {
|
|||||||
while (event_index < self.events.len) {
|
while (event_index < self.events.len) {
|
||||||
var taken = false;
|
var taken = false;
|
||||||
const event: Event = self.events.buffer[event_index];
|
const event: Event = self.events.buffer[event_index];
|
||||||
const is_active = self.isBoxActive(key);
|
const is_active = self.isKeyActive(key);
|
||||||
|
|
||||||
if (event == .mouse_pressed and clickable and is_mouse_inside) {
|
if (event == .mouse_pressed and clickable and is_mouse_inside) {
|
||||||
const mouse_button = event.mouse_pressed;
|
const mouse_button = event.mouse_pressed;
|
||||||
@ -1119,7 +1269,11 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isBoxHot(self: *UI, key: Key) bool {
|
pub fn isBoxHot(self: *UI, box: *Box) bool {
|
||||||
|
return self.isKeyHot(box.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isKeyHot(self: *UI, key: Key) bool {
|
||||||
if (self.hot_box_key) |hot_box_key| {
|
if (self.hot_box_key) |hot_box_key| {
|
||||||
return hot_box_key.eql(key);
|
return hot_box_key.eql(key);
|
||||||
} else {
|
} else {
|
||||||
@ -1127,7 +1281,7 @@ fn isBoxHot(self: *UI, key: Key) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isBoxActive(self: *UI, key: Key) bool {
|
pub fn isKeyActive(self: *UI, key: Key) bool {
|
||||||
inline for (std.meta.fields(rl.MouseButton)) |mouse_button_field| {
|
inline for (std.meta.fields(rl.MouseButton)) |mouse_button_field| {
|
||||||
const mouse_button: rl.MouseButton = @enumFromInt(mouse_button_field.value);
|
const mouse_button: rl.MouseButton = @enumFromInt(mouse_button_field.value);
|
||||||
|
|
||||||
@ -1195,6 +1349,7 @@ pub fn pushScrollbar(self: *UI, key: UI.Key) *Box {
|
|||||||
self.pushParent(container);
|
self.pushParent(container);
|
||||||
|
|
||||||
const content_area = self.newBoxFromString("Scroll area");
|
const content_area = self.newBoxFromString("Scroll area");
|
||||||
|
content_area.flags.insert(.skip_draw);
|
||||||
content_area.flags.insert(.scrollable);
|
content_area.flags.insert(.scrollable);
|
||||||
content_area.size = .{
|
content_area.size = .{
|
||||||
.x = UI.Size.percent(1, 0),
|
.x = UI.Size.percent(1, 0),
|
||||||
@ -1215,6 +1370,14 @@ pub fn popScrollbar(self: *UI) void {
|
|||||||
const content_area = self.getParent().?;
|
const content_area = self.getParent().?;
|
||||||
self.popParent(); // pop scroll area
|
self.popParent(); // pop scroll area
|
||||||
|
|
||||||
|
const visible_content_size = content_area.persistent.size.y;
|
||||||
|
const used_content_size = content_area.persistent.children_size.y;
|
||||||
|
const visible_percent = clamp(visible_content_size / used_content_size, 0, 1);
|
||||||
|
if (used_content_size != 0) {
|
||||||
|
content_area.flags.remove(.skip_draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content_area.flags.contains(.skip_draw)) {
|
||||||
const scrollbar_area = self.newBoxFromString("Scrollbar area");
|
const scrollbar_area = self.newBoxFromString("Scrollbar area");
|
||||||
scrollbar_area.background = rl.Color.gold;
|
scrollbar_area.background = rl.Color.gold;
|
||||||
scrollbar_area.flags.insert(.scrollable);
|
scrollbar_area.flags.insert(.scrollable);
|
||||||
@ -1225,10 +1388,6 @@ pub fn popScrollbar(self: *UI) void {
|
|||||||
self.pushParent(scrollbar_area);
|
self.pushParent(scrollbar_area);
|
||||||
defer self.popParent();
|
defer self.popParent();
|
||||||
|
|
||||||
const visible_content_size = content_area.persistent.size.y;
|
|
||||||
const used_content_size = content_area.persistent.children_size.y;
|
|
||||||
const visible_percent = clamp(visible_content_size / used_content_size, 0, 1);
|
|
||||||
|
|
||||||
const draggable = self.newBoxFromString("Scrollbar button");
|
const draggable = self.newBoxFromString("Scrollbar button");
|
||||||
draggable.background = rl.Color.dark_brown;
|
draggable.background = rl.Color.dark_brown;
|
||||||
draggable.flags.insert(.clickable);
|
draggable.flags.insert(.clickable);
|
||||||
@ -1239,9 +1398,9 @@ pub fn popScrollbar(self: *UI) void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sroll_offset = &content_area.persistent.sroll_offset;
|
const sroll_offset = &content_area.persistent.sroll_offset;
|
||||||
const scrollbar_height = scrollbar_area.persistent.size.y;
|
const scrollbar_height = content_area.persistent.size.y;
|
||||||
const max_offset = scrollbar_height * (1 - visible_percent);
|
const max_offset = scrollbar_height * (1 - visible_percent);
|
||||||
draggable.setFixedY(scrollbar_area.persistent.position.y + sroll_offset.* * max_offset);
|
draggable.setFixedY(content_area.persistent.position.y + sroll_offset.* * max_offset);
|
||||||
|
|
||||||
const draggable_signal = self.signalFromBox(draggable);
|
const draggable_signal = self.signalFromBox(draggable);
|
||||||
if (draggable_signal.dragged()) {
|
if (draggable_signal.dragged()) {
|
||||||
@ -1260,13 +1419,13 @@ pub fn popScrollbar(self: *UI) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sroll_offset.* = clamp(sroll_offset.*, 0, 1);
|
sroll_offset.* = clamp(sroll_offset.*, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
self.popParent(); // pop container
|
self.popParent(); // pop container
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *Box {
|
pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *Box {
|
||||||
const box = self.newBoxFromString(text);
|
const box = self.clickableBox(text);
|
||||||
box.flags.insert(.clickable);
|
|
||||||
box.size.x = UI.Size.text(1, 1);
|
box.size.x = UI.Size.text(1, 1);
|
||||||
box.size.y = UI.Size.text(0.5, 1);
|
box.size.y = UI.Size.text(0.5, 1);
|
||||||
box.setText(font, text);
|
box.setText(font, text);
|
||||||
@ -1274,7 +1433,64 @@ pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *Box {
|
|||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clickableBox(self: *UI, key: []const u8) *Box {
|
||||||
|
const box = self.newBoxFromString(key);
|
||||||
|
box.flags.insert(.clickable);
|
||||||
|
box.flags.insert(.highlight_active);
|
||||||
|
box.flags.insert(.highlight_hot);
|
||||||
|
box.flags.insert(.hover_mouse_hand);
|
||||||
|
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spacer(self: *UI, size: Vec2Size) void {
|
pub fn spacer(self: *UI, size: Vec2Size) void {
|
||||||
const box = self.newBox(UI.Key.initNil());
|
const box = self.newBox(UI.Key.initNil());
|
||||||
box.size = size;
|
box.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pushCenterBox(self: *UI) *Box {
|
||||||
|
self.pushHorizontalAlign();
|
||||||
|
|
||||||
|
const vertical_align = self.newBox(UI.Key.initNil());
|
||||||
|
vertical_align.layout_axis = .Y;
|
||||||
|
vertical_align.size = .{
|
||||||
|
.x = UI.Size.childrenSum(1),
|
||||||
|
.y = UI.Size.percent(1, 0),
|
||||||
|
};
|
||||||
|
self.pushParent(vertical_align);
|
||||||
|
|
||||||
|
self.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
const container = self.newBox(UI.Key.initNil());
|
||||||
|
container.size.x = UI.Size.childrenSum(1);
|
||||||
|
container.size.y = UI.Size.childrenSum(1);
|
||||||
|
self.pushParent(container);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popCenterBox(self: *UI) void {
|
||||||
|
self.popParent();
|
||||||
|
|
||||||
|
self.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
self.popParent();
|
||||||
|
|
||||||
|
self.popHorizontalAlign();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pushHorizontalAlign(self: *UI) void {
|
||||||
|
const horizontal_align = self.newBox(UI.Key.initNil());
|
||||||
|
horizontal_align.layout_axis = .X;
|
||||||
|
horizontal_align.size = .{
|
||||||
|
.x = UI.Size.percent(1, 0),
|
||||||
|
.y = UI.Size.childrenSum(1),
|
||||||
|
};
|
||||||
|
self.pushParent(horizontal_align);
|
||||||
|
|
||||||
|
self.spacer(.{ .x = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popHorizontalAlign(self: *UI) void {
|
||||||
|
self.spacer(.{ .x = UI.Size.percent(1, 0) });
|
||||||
|
self.popParent();
|
||||||
|
}
|
||||||
54
tools/png-to-icon.zig
Normal file
54
tools/png-to-icon.zig
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const stb_image = @import("stb_image");
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/ICO_(file_format)#Icon_file_structure
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
|
const args = try std.process.argsAlloc(allocator);
|
||||||
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
|
if (args.len != 3) {
|
||||||
|
std.debug.print("Usage: ./png-to-icon <png-file> <output-ico>", .{});
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
const input_png_path = args[1];
|
||||||
|
const output_ico_path = args[2];
|
||||||
|
|
||||||
|
const input_png_data = try cwd.readFileAlloc(allocator, input_png_path, 1024 * 1024 * 5);
|
||||||
|
defer allocator.free(input_png_data);
|
||||||
|
|
||||||
|
const png_image = try stb_image.load(input_png_data);
|
||||||
|
defer png_image.deinit();
|
||||||
|
|
||||||
|
std.debug.assert(png_image.width > 0 and png_image.width <= 256);
|
||||||
|
std.debug.assert(png_image.height > 0 and png_image.height <= 256);
|
||||||
|
|
||||||
|
const output_ico_file = try std.fs.cwd().createFile(output_ico_path, .{ });
|
||||||
|
defer output_ico_file.close();
|
||||||
|
|
||||||
|
const writer = output_ico_file.writer();
|
||||||
|
|
||||||
|
// ICONDIR structure
|
||||||
|
try writer.writeInt(u16, 0, .little); // Must always be zero
|
||||||
|
try writer.writeInt(u16, 1, .little); // Image type. 1 for .ICO
|
||||||
|
try writer.writeInt(u16, 1, .little); // Number of images
|
||||||
|
|
||||||
|
// ICONDIRENTRY structure
|
||||||
|
try writer.writeInt(u8, @truncate(png_image.width), .little); // Image width
|
||||||
|
try writer.writeInt(u8, @truncate(png_image.height), .little); // Image height
|
||||||
|
try writer.writeInt(u8, 0, .little); // Number of colors in color pallete. 0 means that color pallete is not used
|
||||||
|
try writer.writeInt(u8, 0, .little); // Must always be zero
|
||||||
|
try writer.writeInt(u16, 0, .little); // Color plane
|
||||||
|
try writer.writeInt(u16, 32, .little); // Bits per pixel
|
||||||
|
try writer.writeInt(u32, @intCast(input_png_data.len), .little); // Image size in bytes
|
||||||
|
try writer.writeInt(u32, 22, .little); // Offset to image data from the start
|
||||||
|
|
||||||
|
// PNG image data
|
||||||
|
try writer.writeAll(input_png_data);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user