Compare commits

..

No commits in common. "7301c68b7e9a9e1026b53e483ce1c511b03f68b2" and "4db4b9fa811313bc022071098b8b09717144fd28" have entirely different histories.

22 changed files with 382 additions and 9113 deletions

View File

@ -4,28 +4,6 @@
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
* 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)"
```
* 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.

114
build.zig
View File

@ -1,105 +1,15 @@
const std = @import("std");
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 {
const target = b.standardTargetOptions(.{});
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", .{
.target = target,
.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(.{
.name = "daq-view",
.root_source_file = b.path("src/main.zig"),
@ -107,28 +17,34 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
});
exe.addIncludePath(b.path("src"));
exe.addCSourceFile(.{
.file = b.path("src/cute_aseprite.c")
});
exe.linkLibrary(raylib_dep.artifact("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");
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
// https://github.com/ziglang/zig/issues/14637#issuecomment-1428689051
if (target.result.os.tag == .windows) {
exe.subsystem = if (optimize == .Debug) .Console else .Windows;
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");
const resource_file = b.addWriteFiles();
const add_icon_step = AddExecutableIcon.init(exe, icon_file);
exe.step.dependOn(&add_icon_step.step);
// https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/
// TODO: Generate icon file at build time
exe.addWin32ResourceFile(.{
.file = resource_file.add("daq-view.rc", "IDI_ICON ICON \"./src/assets/icon.ico\""),
});
exe.linkSystemLibrary("Comdlg32");
}

View File

@ -7,14 +7,6 @@
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
.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 = .{

View File

@ -1,28 +0,0 @@
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] };
}

View File

@ -1,23 +0,0 @@
#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);
}

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,10 @@ const UI = @import("./ui.zig");
const Platform = @import("./platform.zig");
const Assets = @import("./assets.zig");
const Graph = @import("./graph.zig");
const NIDaq = @import("ni-daq/root.zig");
const NIDaq = @import("ni-daq.zig");
const rect_utils = @import("./rect-utils.zig");
const remap = @import("./utils.zig").remap;
const TaskPool = @import("ni-daq/task-pool.zig");
const TaskPool = @import("./task-pool.zig");
const log = std.log.scoped(.app);
const assert = std.debug.assert;
@ -92,79 +92,47 @@ allocator: std.mem.Allocator,
ui: UI,
channel_views: std.BoundedArray(ChannelView, max_channels) = .{},
ni_daq: NIDaq,
task_pool: TaskPool,
loaded_files: [max_channels]?FileChannel = .{ 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 {
channels,
add_from_device
} = .channels,
shown_modal: ?union(enum) {
no_library_error,
library_version_error: std.SemanticVersion,
library_version_warning: std.SemanticVersion
} = null,
device_filter: NIDaq.BoundedDeviceName = .{},
show_voltage_analog_inputs: bool = true,
show_voltage_analog_outputs: bool = true,
selected_channels: std.BoundedArray([:0]u8, max_channels) = .{},
pub fn init(self: *App, allocator: std.mem.Allocator) !void {
var ni_daq = try NIDaq.init(allocator, .{
.max_devices = 4,
.max_analog_inputs = 32,
.max_analog_outputs = 8,
.max_counter_outputs = 8,
.max_counter_inputs = 8,
.max_analog_input_voltage_ranges = 4,
.max_analog_output_voltage_ranges = 4
});
errdefer ni_daq.deinit(allocator);
self.* = App{
.allocator = allocator,
.ui = UI.init(allocator),
.ni_daq = ni_daq,
.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_analog_inputs = 32,
.max_analog_outputs = 8,
.max_counter_outputs = 8,
.max_counter_inputs = 8,
.max_analog_input_voltage_ranges = 4,
.max_analog_output_voltage_ranges = 4
});
self.ni_daq = ni_daq;
const installed_version = try ni_daq.version();
if (installed_version.order(NIDaq.Api.min_version) == .lt) {
self.shown_modal = .{ .library_version_warning = installed_version };
}
} 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);
try TaskPool.init(&self.task_pool, allocator, &self.ni_daq);
errdefer self.task_pool.deinit();
}
pub fn deinit(self: *App) void {
self.task_pool.deinit();
for (self.channel_views.slice()) |*channel| {
channel.view_cache.deinit();
}
@ -189,9 +157,39 @@ pub fn deinit(self: *App) void {
self.selected_channels.len = 0;
self.ui.deinit();
self.ni_daq.deinit(self.allocator);
}
self.task_pool.deinit();
if (self.ni_daq) |*ni_daq| ni_daq.deinit(self.allocator);
fn showButton(self: *App, text: []const u8) UI.Interaction {
var button = self.ui.newWidget(self.ui.keyFromString(text));
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 {
@ -274,8 +272,6 @@ pub fn appendChannelFromFile(self: *App, path: []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 name_buff = try DeviceChannel.Name.fromSlice(channel_name);
@ -287,17 +283,17 @@ pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
var min_value: f64 = 0;
var max_value: f64 = 1;
const voltage_ranges = try ni_daq.listDeviceAOVoltageRanges(device_z);
const voltage_ranges = try self.ni_daq.listDeviceAOVoltageRanges(device_z);
if (voltage_ranges.len > 0) {
min_value = voltage_ranges[0].low;
max_value = voltage_ranges[0].high;
}
const max_sample_rate = try ni_daq.getMaxSampleRate(channel_name_z);
const max_sample_rate = try self.ni_daq.getMaxSampleRate(channel_name_z);
self.device_channels[device_channel_index] = DeviceChannel{
.name = name_buff,
.min_sample_rate = ni_daq.getMinSampleRate(channel_name_z) catch max_sample_rate,
.min_sample_rate = self.ni_daq.getMinSampleRate(channel_name_z) catch max_sample_rate,
.max_sample_rate = max_sample_rate,
.min_value = min_value,
.max_value = max_value,
@ -338,17 +334,6 @@ fn getChannelSource(self: *App, channel_view: *ChannelView) ?ChannelView.SourceO
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 {
const min_visible_samples = 1; // sample_count*0.02;
@ -362,23 +347,26 @@ fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count
const minimap_rect = minimap_box.computedRect();
const middle_box = self.ui.clickableBox("Middle knob");
const middle_box = self.ui.newBoxFromString("Middle knob");
{
middle_box.flags.insert(.clickable);
middle_box.flags.insert(.draggable_x);
middle_box.background = rl.Color.black.alpha(0.5);
middle_box.size.y = UI.Size.pixels(32, 1);
}
const left_knob_box = self.ui.clickableBox("Left knob");
const left_knob_box = self.ui.newBoxFromString("Left knob");
{
left_knob_box.flags.insert(.clickable);
left_knob_box.flags.insert(.draggable_x);
left_knob_box.background = rl.Color.black.alpha(0.5);
left_knob_box.size.x = UI.Size.pixels(8, 1);
left_knob_box.size.y = UI.Size.pixels(32, 1);
}
const right_knob_box = self.ui.clickableBox("Right knob");
const right_knob_box = self.ui.newBoxFromString("Right knob");
{
right_knob_box.flags.insert(.clickable);
right_knob_box.flags.insert(.draggable_x);
right_knob_box.background = rl.Color.black.alpha(0.5);
right_knob_box.size.x = UI.Size.pixels(8, 1);
@ -472,11 +460,15 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
if (source == .device) {
const device_channel = source.device;
if (self.ni_daq) |*ni_daq| {
const record_button = self.ui.button(.text, "Record");
{
const record_button = self.ui.newBoxFromString("Record");
record_button.flags.insert(.clickable);
record_button.size.x = UI.Size.text(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");
}
@ -488,7 +480,6 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
} else {
const channel_name = device_channel.name.buffer[0..device_channel.name.len :0];
device_channel.active_task = try self.task_pool.launchAIVoltageChannel(
ni_daq,
&device_channel.mutex,
&device_channel.samples,
.{
@ -508,11 +499,11 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
}
{
const follow_button = self.ui.button(.text, "Follow");
const follow_button = self.ui.newBoxFromString("Follow");
follow_button.flags.insert(.clickable);
follow_button.size.x = UI.Size.text(1, 0);
follow_button.size.y = UI.Size.percent(1, 0);
if (channel_view.follow) {
follow_button.setText(.text, "Unfollow");
}
follow_button.setText(.text, if (channel_view.follow) "Unfollow" else "Follow");
const signal = self.ui.signalFromBox(follow_button);
if (signal.clicked()) {
@ -553,15 +544,13 @@ fn showChannelsWindow(self: *App) !void {
{
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.y = UI.Size.percent(1, 1);
prompt_box.size.y = UI.Size.pixels(150, 1);
self.ui.pushParent(prompt_box);
defer self.ui.popParent();
const center_box = self.ui.pushCenterBox();
defer self.ui.popCenterBox();
center_box.layout_axis = .X;
center_box.layout_gap = 32;
self.ui.spacer(.{ .x = UI.Size.percent(1, 0) });
const from_file_button = self.ui.button(.text, "Add from file");
from_file_button.background = srcery.green;
@ -569,17 +558,28 @@ fn showChannelsWindow(self: *App) !void {
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");
from_device_button.background = srcery.green;
if (self.ui.signalFromBox(from_device_button).clicked()) {
log.debug("TODO: Not implemented", .{});
}
self.ui.spacer(.{ .x = UI.Size.percent(1, 0) });
}
}
fn showAddFromDeviceWindow(self: *App) !void {
const ni_daq = &(self.ni_daq orelse return);
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 {
const window = self.ui.newBoxFromString("Device window");
window.size.x = UI.Size.percent(1, 0);
window.size.y = UI.Size.percent(1, 0);
@ -595,10 +595,12 @@ fn showAddFromDeviceWindow(self: *App) !void {
self.ui.pushParent(filters_box);
defer self.ui.popParent();
for (try ni_daq.listDeviceNames()) |device| {
const device_box = self.ui.button(.text, device);
for (try self.ni_daq.listDeviceNames()) |device| {
const device_box = self.ui.newBoxFromString(device);
device_box.flags.insert(.clickable);
device_box.size.x = 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);
if (signal.clicked()) {
@ -607,33 +609,37 @@ fn showAddFromDeviceWindow(self: *App) !void {
}
{
const toggle_inputs_box = self.ui.button(.text, "Toggle inputs");
const toggle_inputs_box = self.ui.newBoxFromString("Toggle inputs");
toggle_inputs_box.flags.insert(.clickable);
toggle_inputs_box.size.x = 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");
if (self.ui.signalFromBox(toggle_inputs_box).clicked()) {
const signal = self.ui.signalFromBox(toggle_inputs_box);
if (signal.clicked()) {
self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs;
}
}
{
const toggle_outputs_box = self.ui.button(.text, "Toggle outputs");
const toggle_outputs_box = self.ui.newBoxFromString("Toggle outputs");
toggle_outputs_box.flags.insert(.clickable);
toggle_outputs_box.size.x = 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");
if (self.ui.signalFromBox(toggle_outputs_box).clicked()) {
const signal = self.ui.signalFromBox(toggle_outputs_box);
if (signal.clicked()) {
self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs;
}
}
{
const add_button = self.ui.button(.text, "Add selected");
const add_button = self.ui.newBoxFromString("Add");
add_button.flags.insert(.clickable);
add_button.size.x = UI.Size.text(2, 1);
add_button.size.y = UI.Size.text(2, 1);
if (self.ui.signalFromBox(add_button).clicked()) {
add_button.setText(.text, "Add selected");
const signal = self.ui.signalFromBox(add_button);
if (signal.clicked()) {
const selected_devices = self.selected_channels.constSlice();
for (selected_devices) |channel| {
@ -662,21 +668,21 @@ fn showAddFromDeviceWindow(self: *App) !void {
self.device_filter.buffer[0..self.device_filter.len :0]
};
} else {
devices = try ni_daq.listDeviceNames();
devices = try self.ni_daq.listDeviceNames();
}
for (devices) |device| {
var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
if (self.show_voltage_analog_inputs) {
if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
ai_voltage_physical_channels = try ni_daq.listDeviceAIPhysicalChannels(device);
if (try self.ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
ai_voltage_physical_channels = try self.ni_daq.listDeviceAIPhysicalChannels(device);
}
}
var ao_physical_channels: []const [:0]const u8 = &.{};
if (self.show_voltage_analog_outputs) {
if (try ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
ao_physical_channels = try ni_daq.listDeviceAOPhysicalChannels(device);
if (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device);
}
}
@ -684,7 +690,11 @@ fn showAddFromDeviceWindow(self: *App) !void {
for (channels) |channel| {
const selected_channels_slice = self.selected_channels.constSlice();
const channel_box = self.ui.button(.text, channel);
const channel_box = self.ui.newBoxFromString(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) {
channel_box.background = srcery.xgray3;
@ -707,6 +717,7 @@ fn showAddFromDeviceWindow(self: *App) !void {
fn showToolbar(self: *App) void {
const toolbar = self.ui.newBoxFromString("Toolbar");
toolbar.flags.insert(.clickable);
toolbar.background = rl.Color.green;
toolbar.layout_axis = .X;
toolbar.size = .{
@ -717,9 +728,14 @@ fn showToolbar(self: *App) void {
defer self.ui.popParent();
{
const box = self.ui.button(.text, "Add from file");
const box = self.ui.newBoxFromString("Add from file");
box.flags.insert(.clickable);
box.background = rl.Color.red;
box.size.y = UI.Size.percent(1, 1);
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText(.text, "Add from file",);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
@ -736,9 +752,14 @@ fn showToolbar(self: *App) void {
}
{
const box = self.ui.button(.text, "Add from device");
const box = self.ui.newBoxFromString("Add from device");
box.flags.insert(.clickable);
box.background = rl.Color.lime;
box.size.y = UI.Size.percent(1, 1);
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText(.text, "Add from device");
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
@ -751,186 +772,6 @@ 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 {
self.ui.begin();
defer self.ui.end();
@ -938,32 +779,6 @@ fn updateUI(self: *App) !void {
const root_box = self.ui.getParent().?;
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();
if (self.shown_window == .channels) {
@ -971,10 +786,6 @@ fn updateUI(self: *App) !void {
} else if (self.shown_window == .add_from_device) {
try self.showAddFromDeviceWindow();
}
if (maybe_modal_overlay) |box| {
self.ui.appendBox(box);
}
}
pub fn tick(self: *App) !void {
@ -1011,6 +822,7 @@ pub fn tick(self: *App) !void {
}
}
// On the first frame, render the UI twice.
// So that on the second pass widgets that depend on sizes from other widgets have settled
if (self.ui.frame_index == 0) {

View File

@ -2,7 +2,7 @@ const std = @import("std");
const rl = @import("raylib");
const srcery = @import("./srcery.zig");
const FontFace = @import("./font-face.zig");
const Aseprite = @import("cute_aseprite");
const Aseprite = @import("./aseprite.zig");
const assert = std.debug.assert;
@ -60,18 +60,12 @@ pub fn init(allocator: std.mem.Allocator) !void {
}
fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font {
var codepoints: std.BoundedArray(i32, 128) = .{};
for (0..95) |i| {
codepoints.appendAssumeCapacity(@as(i32, @intCast(i)) + 32);
var codepoints: [95]i32 = undefined;
for (0..codepoints.len) |i| {
codepoints[i] = @as(i32, @intCast(i)) + 32;
}
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());
const loaded_font = rl.loadFontFromMemory(".ttf", ttf_data, @intCast(font_size), &codepoints);
if (!loaded_font.isReady()) {
return error.LoadFontFromMemory;
}

BIN
src/assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

View File

@ -134,10 +134,6 @@ pub fn measureText(self: @This(), text: []const u8) rl.Vector2 {
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 {
const text_size = self.measureText(text);
const adjusted_position = rl.Vector2{

View File

@ -4,7 +4,6 @@ const builtin = @import("builtin");
const Application = @import("./app.zig");
const Assets = @import("./assets.zig");
const Profiler = @import("./profiler.zig");
const Platform = @import("./platform.zig");
const raylib_h = @cImport({
@cInclude("stdio.h");
@cInclude("raylib.h");
@ -65,8 +64,6 @@ fn raylibTraceLogCallback(logType: c_int, text: [*c]const u8, args: raylib_h.va_
}
pub fn main() !void {
Platform.init();
// TODO: Setup logging to a file
raylib_h.SetTraceLogCallback(raylibTraceLogCallback);
rl.setTraceLogLevel(toRaylibLogLevel(std.options.log_level));
@ -156,7 +153,7 @@ pub fn main() !void {
defer app.deinit();
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_IjStim.bin");
}
@ -194,5 +191,5 @@ pub fn main() !void {
}
test {
_ = @import("./ni-daq/root.zig");
_ = @import("./ni-daq.zig");
}

View File

@ -1,6 +1,9 @@
const std = @import("std");
pub const Api = @import("./api.zig");
pub const c = Api.c;
pub const c = @cImport({
@cInclude("stdint.h");
@cDefine("__int64", "long long");
@cInclude("NIDAQmx.h");
});
const assert = std.debug.assert;
const log = std.log.scoped(.ni_daq);
@ -35,7 +38,6 @@ pub const Options = struct {
};
pub const Task = struct {
ni_daq: *NIDaq,
handle: TaskHandle,
name_buffer: [max_task_name_size]u8 = undefined,
@ -43,23 +45,19 @@ pub const Task = struct {
dropped_samples: u32 = 0,
pub fn clear(self: Task) void {
// Ignore error
const api = self.ni_daq.api;
self.ni_daq.logDAQmxError(api.DAQmxClearTask(self.handle));
logDAQmxError(c.DAQmxClearTask(self.handle));
}
pub fn name(self: *Task) ![]const u8 {
const api = self.ni_daq.api;
const required_size = api.DAQmxGetTaskName(self.handle, null, 0);
const required_size = c.DAQmxGetTaskName(self.handle, null, 0);
assert(required_size >= 0);
if (required_size > self.name_buffer.len) {
return error.BufferTooSmall;
}
try self.ni_daq.checkDAQmxError(
api.DAQmxGetTaskName(
try checkDAQmxError(
c.DAQmxGetTaskName(
self.handle,
&self.name_buffer,
self.name_buffer.len
@ -71,33 +69,29 @@ pub const Task = struct {
}
pub fn start(self: Task) !void {
const api = self.ni_daq.api;
try self.ni_daq.checkDAQmxError(
api.DAQmxStartTask(self.handle),
try checkDAQmxError(
c.DAQmxStartTask(self.handle),
error.DAQmxStartTask
);
}
pub fn stop(self: Task) !void {
const api = self.ni_daq.api;
try self.ni_daq.checkDAQmxError(
api.DAQmxStopTask(self.handle),
try checkDAQmxError(
c.DAQmxStopTask(self.handle),
error.DAQmxStopTask
);
}
pub fn setContinousSampleRate(self: Task, sample_rate: f64) !void {
const api = self.ni_daq.api;
try self.ni_daq.checkDAQmxError(
api.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_ContSamps, 0),
try checkDAQmxError(
c.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_ContSamps, 0),
error.DAQmxCfgSampClkTiming
);
}
pub fn setFiniteSampleRate(self: Task, sample_rate: f64, samples_per_channel: u64) !void {
const api = self.ni_daq.api;
try self.ni_daq.checkDAQmxError(
api.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_FiniteSamps, samples_per_channel),
try checkDAQmxError(
c.DAQmxCfgSampClkTiming(self.handle, null, sample_rate, c.DAQmx_Val_Rising, c.DAQmx_Val_FiniteSamps, samples_per_channel),
error.DAQmxCfgSampClkTiming
);
}
@ -113,9 +107,8 @@ pub const Task = struct {
};
pub fn createAIVoltageChannel(self: Task, options: AIVoltageChannelOptions) !void {
const api = self.ni_daq.api;
try self.ni_daq.checkDAQmxError(
api.DAQmxCreateAIVoltageChan(
try checkDAQmxError(
c.DAQmxCreateAIVoltageChan(
self.handle,
options.channel,
options.assigned_name,
@ -139,7 +132,7 @@ pub const Task = struct {
};
pub fn createAOVoltageChannel(self: Task, options: AOVoltageChannelOptions) !void {
try self.ni_daq.checkDAQmxError(
try checkDAQmxError(
c.DAQmxCreateAOVoltageChan(
self.handle,
options.channel,
@ -162,9 +155,8 @@ pub const Task = struct {
};
pub fn readAnalog(self: *Task, options: ReadAnalogOptions) !u32 {
const api = self.ni_daq.api;
var samples_per_channel: i32 = 0;
const err = api.DAQmxReadAnalogF64(
const err = c.DAQmxReadAnalogF64(
self.handle,
options.samples_per_channel,
options.timeout,
@ -179,7 +171,7 @@ pub const Task = struct {
self.dropped_samples += 1;
log.err("Dropped samples, not reading samples fast enough.", .{});
} else if (err < 0) {
try self.ni_daq.checkDAQmxError(err, error.DAQmxReadAnalogF64);
try checkDAQmxError(err, error.DAQmxReadAnalogF64);
}
return @intCast(samples_per_channel);
@ -187,7 +179,7 @@ pub const Task = struct {
pub fn isDone(self: Task) !bool {
var result: c.bool32 = 0;
try self.ni_daq.checkDAQmxError(
try checkDAQmxError(
c.DAQmxIsTaskDone(self.handle, &result),
error.DAQmxIsTaskDone
);
@ -373,13 +365,12 @@ const DeviceBuffers = struct {
};
options: Options,
api: *Api,
device_names_buffer: []u8,
device_names: StringArrayListUnmanaged,
device_buffers: []DeviceBuffers,
pub fn init(allocator: std.mem.Allocator, api: *Api, options: Options) !NIDaq {
pub fn init(allocator: std.mem.Allocator, options: Options) !NIDaq {
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);
errdefer allocator.free(device_names_buffer);
@ -397,7 +388,6 @@ pub fn init(allocator: std.mem.Allocator, api: *Api, options: Options) !NIDaq {
return NIDaq{
.options = options,
.api = api,
.device_names_buffer = device_names_buffer,
.device_names = device_names,
.device_buffers = device_buffers
@ -433,33 +423,33 @@ test {
try std.testing.expectEqual(3, maxIndexStringLength(101));
}
pub fn logDAQmxError(self: NIDaq, error_code: i32) void {
pub fn logDAQmxError(error_code: i32) void {
if (error_code == 0) {
return;
}
var msg: [512:0]u8 = .{ 0 } ** 512;
if (self.api.DAQmxGetErrorString(error_code, &msg, msg.len) == 0) {
if (c.DAQmxGetErrorString(error_code, &msg, msg.len) == 0) {
log.err("DAQmx ({}): {s}", .{error_code, msg});
} else {
log.err("DAQmx ({}): Unknown (Buffer too small for message)", .{error_code});
}
}
pub fn checkDAQmxError(self: NIDaq, error_code: i32, err: anyerror) !void {
self.logDAQmxError(error_code);
pub fn checkDAQmxError(error_code: i32, err: anyerror) !void {
logDAQmxError(error_code);
if (error_code < 0) {
return err;
}
}
pub fn checkDAQmxErrorIgnoreWarnings(self: NIDaq, error_code: i32, err: anyerror) !void {
pub fn checkDAQmxErrorIgnoreWarnings(error_code: i32, err: anyerror) !void {
if (error_code > 0) {
return;
}
try self.checkDAQmxError(error_code, err);
try checkDAQmxError(error_code, err);
}
fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), buffer: []u8) !void {
@ -481,22 +471,22 @@ fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), bu
}
}
pub fn version(self: NIDaq) !std.SemanticVersion {
pub fn version() !std.SemanticVersion {
var major: u32 = 0;
try self.checkDAQmxError(
self.api.DAQmxGetSysNIDAQMajorVersion(&major),
try checkDAQmxError(
c.DAQmxGetSysNIDAQMajorVersion(&major),
error.GetMajorVersion
);
var minor: u32 = 0;
try self.checkDAQmxError(
self.api.DAQmxGetSysNIDAQMinorVersion(&minor),
try checkDAQmxError(
c.DAQmxGetSysNIDAQMinorVersion(&minor),
error.GetMinorVersion
);
var update: u32 = 0;
try self.checkDAQmxError(
self.api.DAQmxGetSysNIDAQUpdateVersion(&update),
try checkDAQmxError(
c.DAQmxGetSysNIDAQUpdateVersion(&update),
error.GetUpdateVersion
);
@ -511,8 +501,8 @@ pub fn listDeviceNames(self: *NIDaq) ![]const [:0]const u8 {
self.device_names.clearRetainingCapacity();
self.clearAllDeviceBuffers();
const required_size = self.api.DAQmxGetSysDevNames(null, 0);
try self.checkDAQmxErrorIgnoreWarnings(required_size, error.DAQmxGetSysDevNames);
const required_size = c.DAQmxGetSysDevNames(null, 0);
try checkDAQmxErrorIgnoreWarnings(required_size, error.DAQmxGetSysDevNames);
if (required_size == 0) {
return self.device_names.items;
}
@ -521,8 +511,8 @@ pub fn listDeviceNames(self: *NIDaq) ![]const [:0]const u8 {
return error.BufferTooSmall;
}
try self.checkDAQmxError(
self.api.DAQmxGetSysDevNames(self.device_names_buffer.ptr, @intCast(self.device_names_buffer.len)),
try checkDAQmxError(
c.DAQmxGetSysDevNames(self.device_names_buffer.ptr, @intCast(self.device_names_buffer.len)),
error.GetDeviceNames
);
@ -559,7 +549,6 @@ fn clearAllDeviceBuffers(self: *NIDaq) void {
}
fn listDevicePhysicalChannels(
self: NIDaq,
getPhysicalChannels: anytype,
device: [:0]const u8,
channel_names: *DeviceBuffers.ChannelNames,
@ -570,7 +559,7 @@ fn listDevicePhysicalChannels(
array_list.clearRetainingCapacity();
const required_size = getPhysicalChannels(device, null, 0);
try self.checkDAQmxErrorIgnoreWarnings(required_size, error.GetPhysicalChannels);
try checkDAQmxErrorIgnoreWarnings(required_size, error.GetPhysicalChannels);
if (required_size == 0) {
return array_list.items;
}
@ -579,7 +568,7 @@ fn listDevicePhysicalChannels(
return error.BufferTooSmall;
}
try self.checkDAQmxError(
try checkDAQmxError(
getPhysicalChannels(device, buffer.ptr, @intCast(buffer.len)),
error.GetPhysicalChannels
);
@ -595,8 +584,8 @@ fn listDevicePhysicalChannels(
pub fn listDeviceAIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
var device_buffers = try self.getDeviceBuffers(device);
return self.listDevicePhysicalChannels(
self.api.DAQmxGetDevAIPhysicalChans,
return listDevicePhysicalChannels(
c.DAQmxGetDevAIPhysicalChans,
device,
&device_buffers.analog_input_names
);
@ -605,8 +594,8 @@ pub fn listDeviceAIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const
pub fn listDeviceAOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
var device_buffers = try self.getDeviceBuffers(device);
return self.listDevicePhysicalChannels(
self.api.DAQmxGetDevAOPhysicalChans,
return listDevicePhysicalChannels(
c.DAQmxGetDevAOPhysicalChans,
device,
&device_buffers.analog_output_names
);
@ -615,7 +604,7 @@ pub fn listDeviceAOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const
pub fn listDeviceCOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
var device_buffers = try self.getDeviceBuffers(device);
return self.listDevicePhysicalChannels(
return listDevicePhysicalChannels(
c.DAQmxGetDevCOPhysicalChans,
device,
&device_buffers.counter_output_names
@ -625,7 +614,7 @@ pub fn listDeviceCOPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const
pub fn listDeviceCIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const [:0]const u8 {
var device_buffers = try self.getDeviceBuffers(device);
return self.listDevicePhysicalChannels(
return listDevicePhysicalChannels(
c.DAQmxGetDevCIPhysicalChans,
device,
&device_buffers.counter_input_names
@ -657,19 +646,21 @@ fn getChannelType(device: [:0]const u8) ?ChannelType {
}
pub fn getMinSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
_ = self;
var result: f64 = 0;
const channel_type = getChannelType(channel) orelse return error.UnknownChannelType;
switch (channel_type) {
.analog_input => {
try self.checkDAQmxError(
self.api.DAQmxGetDevAIMinRate(channel, &result),
try checkDAQmxError(
c.DAQmxGetDevAIMinRate(channel, &result),
error.DAQmxGetDevAIMinRate
);
},
.analog_output => {
try self.checkDAQmxError(
self.api.DAQmxGetDevAOMinRate(channel, &result),
try checkDAQmxError(
c.DAQmxGetDevAOMinRate(channel, &result),
error.DAQmxGetDevAOMinRate
);
},
@ -683,19 +674,21 @@ pub fn getMinSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
}
pub fn getMaxSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
_ = self;
var result: f64 = 0;
const channel_type = getChannelType(channel) orelse return error.UnknownChannelType;
switch (channel_type) {
.analog_input => {
try self.checkDAQmxError(
self.api.DAQmxGetDevAIMaxSingleChanRate(channel, &result),
try checkDAQmxError(
c.DAQmxGetDevAIMaxSingleChanRate(channel, &result),
error.DAQmxGetDevAIMaxSingleChanRate
);
},
.analog_output => {
try self.checkDAQmxError(
self.api.DAQmxGetDevAOMaxRate(channel, &result),
try checkDAQmxError(
c.DAQmxGetDevAOMaxRate(channel, &result),
error.DAQmxGetDevAOMaxRate
);
},
@ -709,7 +702,6 @@ pub fn getMaxSampleRate(self: NIDaq, channel: [:0]const u8) !f64 {
}
fn listDeviceVoltageRanges(
self: NIDaq,
getVoltageRanges: anytype,
device: [:0]const u8,
voltage_ranges: *std.ArrayListUnmanaged(Range)
@ -717,7 +709,7 @@ fn listDeviceVoltageRanges(
voltage_ranges.clearRetainingCapacity();
const count = getVoltageRanges(device, null, 0);
try self.checkDAQmxErrorIgnoreWarnings(count, error.GetVoltageRanges);
try checkDAQmxErrorIgnoreWarnings(count, error.GetVoltageRanges);
if (count == 0) {
return voltage_ranges.items;
}
@ -730,7 +722,7 @@ fn listDeviceVoltageRanges(
return error.BufferTooSmall;
}
try self.checkDAQmxError(
try checkDAQmxError(
getVoltageRanges(device, buffer, @intCast(buffer_len)),
error.GetVoltageRanges
);
@ -743,8 +735,8 @@ fn listDeviceVoltageRanges(
pub fn listDeviceAIVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
var device_buffers = try self.getDeviceBuffers(device);
return self.listDeviceVoltageRanges(
self.api.DAQmxGetDevAIVoltageRngs,
return listDeviceVoltageRanges(
c.DAQmxGetDevAIVoltageRngs,
device,
&device_buffers.analog_input_voltage_ranges
);
@ -753,33 +745,36 @@ pub fn listDeviceAIVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
pub fn listDeviceAOVoltageRanges(self: *NIDaq, device: [:0]const u8) ![]Range {
var device_buffers = try self.getDeviceBuffers(device);
return self.listDeviceVoltageRanges(
self.api.DAQmxGetDevAOVoltageRngs,
return listDeviceVoltageRanges(
c.DAQmxGetDevAOVoltageRngs,
device,
&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;
try self.checkDAQmxError(
self.api.DAQmxCreateTask(name orelse "", &handle),
try checkDAQmxError(
c.DAQmxCreateTask(name orelse "", &handle),
error.DAQmxCreateTask
);
return Task{ .ni_daq = self, .handle = handle };
return Task{ .handle = handle };
}
pub fn listDeviceAIMeasurementTypes(self: NIDaq, device: [:0]const u8) !AIMeasurementTypeList {
var result = AIMeasurementTypeList.init(0) catch unreachable;
_ = self;
const count = self.api.DAQmxGetDevAISupportedMeasTypes(device, null, 0);
try self.checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAISupportedMeasTypes);
const count = c.DAQmxGetDevAISupportedMeasTypes(device, null, 0);
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAISupportedMeasTypes);
assert(count <= result.buffer.len);
try self.checkDAQmxError(
self.api.DAQmxGetDevAISupportedMeasTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
try checkDAQmxError(
c.DAQmxGetDevAISupportedMeasTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
error.DAQmxGetDevAISupportedMeasTypes
);
@ -795,13 +790,14 @@ pub fn checkDeviceAIMeasurementType(self: NIDaq, device: [:0]const u8, measureme
pub fn listDeviceAOOutputTypes(self: NIDaq, device: [:0]const u8) !AOOutputTypeList {
var result = AOOutputTypeList.init(0) catch unreachable;
_ = self;
const count = self.api.DAQmxGetDevAOSupportedOutputTypes(device, null, 0);
try self.checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAOSupportedOutputTypes);
const count = c.DAQmxGetDevAOSupportedOutputTypes(device, null, 0);
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAOSupportedOutputTypes);
assert(count <= result.buffer.len);
try self.checkDAQmxError(
self.api.DAQmxGetDevAOSupportedOutputTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
try checkDAQmxError(
c.DAQmxGetDevAOSupportedOutputTypes(device, @as([*c]c_int, @ptrCast(&result.buffer)), result.buffer.len),
error.DAQmxGetDevAOSupportedOutputTypes
);
@ -816,9 +812,11 @@ pub fn checkDeviceAOOutputType(self: NIDaq, device: [:0]const u8, output_type: A
}
pub fn getDeviceProductCategory(self: NIDaq, device: [:0]const u8) !ProductCategory {
_ = self;
var product_category = ProductCategory.Unknown;
try self.checkDAQmxError(
self.api.DAQmxGetDevProductCategory(device, @ptrCast(&product_category)),
try checkDAQmxError(
c.DAQmxGetDevProductCategory(device, @ptrCast(&product_category)),
error.DAQmxGetDevProductCategory
);

View File

@ -1,100 +0,0 @@
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
};
}

View File

@ -132,10 +132,4 @@ pub fn openFilePicker() !std.fs.File {
// TODO: Use the `openFileAbsoluteW` function.
// Could not get it to work, because it always threw OBJECT_PATH_SYNTAX_BAD error
return try std.fs.openFileAbsolute(filename, .{ });
}
pub fn init() void {
if (builtin.os.tag == .windows) {
_ = windows_h.SetConsoleOutputCP(65001);
}
}

View File

@ -1,5 +1,5 @@
const std = @import("std");
const NIDaq = @import("./root.zig");
const NIDaq = @import("./ni-daq.zig");
const assert = std.debug.assert;
const log = std.log.scoped(.task_pool);
@ -43,10 +43,12 @@ pub const Entry = struct {
running: bool = false,
read_thread: std.Thread,
ni_daq: *NIDaq,
entries: [max_tasks]Entry = undefined,
pub fn init(self: *TaskPool, allocator: std.mem.Allocator) !void {
pub fn init(self: *TaskPool, allocator: std.mem.Allocator, ni_daq: *NIDaq) !void {
self.* = TaskPool{
.ni_daq = ni_daq,
.read_thread = undefined
};
@ -130,13 +132,12 @@ fn findFreeEntry(self: *TaskPool) ?*Entry {
pub fn launchAIVoltageChannel(
self: *TaskPool,
ni_daq: *NIDaq,
mutex: *std.Thread.Mutex,
samples: *std.ArrayList(f64),
sampling: Sampling,
options: NIDaq.Task.AIVoltageChannelOptions
) !*Entry {
const task = try ni_daq.createTask(null);
const task = try self.ni_daq.createTask(null);
errdefer task.clear();
const entry = self.findFreeEntry() orelse return error.NotEnoughSpace;

View File

@ -3,7 +3,6 @@ const rl = @import("raylib");
const Assets = @import("./assets.zig");
const rect_utils = @import("./rect-utils.zig");
const srcery = @import("./srcery.zig");
const FontFace = @import("./font-face.zig");
const log = std.log.scoped(.ui);
const assert = std.debug.assert;
@ -243,9 +242,6 @@ pub const Box = struct {
pub const Flag = enum {
clickable,
highlight_hot,
highlight_active,
draggable_x,
draggable_y,
@ -254,15 +250,7 @@ pub const Box = struct {
fixed_x,
fixed_y,
fixed_width,
fixed_height,
hover_mouse_hand,
skip_draw,
text_underline,
text_wrapping,
text_left_align
fixed_height
};
pub const Flags = std.EnumSet(Flag);
@ -317,19 +305,7 @@ pub const Box = struct {
};
}
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 {
pub fn setAllocText(self: *Box, font: Assets.FontId, comptime fmt: []const u8, args: anytype) void {
self.text = .{
.content = std.fmt.allocPrint(self.allocator, fmt, args) catch return,
.font = font
@ -567,7 +543,7 @@ pub fn end(self: *UI) void {
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ew));
} else if (active_box_flags.contains(.draggable_y)) {
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ns));
} else if (hover_box_flags.contains(.hover_mouse_hand)) {
} else if (hover_box_flags.contains(.clickable)) {
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_pointing_hand));
} else {
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_default));
@ -669,10 +645,6 @@ pub fn draw(self: *UI) void {
}
fn drawBox(self: *UI, box: *Box) void {
if (box.flags.contains(.skip_draw)) {
return;
}
const box_rect = box.computedRect();
const do_scissor = box.hasClipping();
@ -690,14 +662,10 @@ fn drawBox(self: *UI, box: *Box) void {
rl.drawRectangleRec(box_rect, background);
}
if (self.isKeyActive(box.key)) {
if (box.flags.contains(.highlight_active)) {
rl.drawRectangleLinesEx(box_rect, 2, rl.Color.orange);
}
} else if (self.isKeyHot(box.key)) {
if (box.flags.contains(.highlight_hot)) {
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.blue);
}
if (self.isBoxActive(box.key)) {
rl.drawRectangleLinesEx(box_rect, 2, rl.Color.orange);
} else if (self.isBoxHot(box.key)) {
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.blue);
}
if (box.texture) |texture| {
@ -718,38 +686,7 @@ fn drawBox(self: *UI, box: *Box) void {
if (box.text) |text| {
const font = Assets.font(text.font);
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
);
}
font.drawTextCenter(text.content, rect_utils.center(box_rect), text.color);
}
var child_iter = self.iterChildrenByParent(box);
@ -758,9 +695,9 @@ fn drawBox(self: *UI, box: *Box) void {
}
if (debug) {
if (self.isKeyActive(box.key)) {
if (self.isBoxActive(box.key)) {
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
} else if (self.isKeyHot(box.key)) {
} else if (self.isBoxHot(box.key)) {
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.orange);
} else {
rl.drawRectangleLinesEx(box_rect, 1, rl.Color.pink);
@ -853,8 +790,6 @@ fn calcLayoutDownardsSize(self: *UI, box: *Box, axis: Axis) void {
const computed_size = getVec2Axis(&box.persistent.size, axis);
if (size.kind == .children_sum) {
var first_child: bool = true;
var sum: f32 = 0;
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
@ -864,36 +799,83 @@ fn calcLayoutDownardsSize(self: *UI, box: *Box, axis: Axis) void {
if (box.layout_axis == axis) {
sum += child_size;
if (!first_child) {
sum += box.layout_gap;
}
} else {
sum = @max(sum, child_size);
}
first_child = false;
}
computed_size.* = sum;
}
}
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);
fn calcLayoutPositions(self: *UI, box: *Box, axis: Axis) void {
{
var layout_position: f32 = 0;
var max_width = box.persistent.size.x;
if (axis_size.kind == .text) {
max_width -= axis_size.kind.text * font.getSize();
}
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
const child_axis_position = getVec2Axis(&child.persistent.position, axis);
if (wrapText(box.allocator, font, text.content, max_width) catch null) |wrapped_content| {
box.text.?.content = wrapped_content;
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 {
// Children can't be wider than the parent on the secondary axis
if (box.layout_axis != axis) {
const max_child_size = getVec2Axis(&box.persistent.size, axis).*;
@ -967,134 +949,12 @@ fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
}
}
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);
}
}
}
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);
var parent_hash: u64 = 0;
if (self.getParent()) |parent| {
parent_hash = parent.key.hash;
}
return Key.initString(parent_hash, text);
}
pub fn newBoxFromString(self: *UI, text: []const u8) *Box {
@ -1105,26 +965,7 @@ pub fn newBoxFromPtr(self: *UI, ptr: anytype) *Box {
return self.newBox(Key.initPtr(ptr));
}
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 {
pub fn newBox(self: *UI, key: Key) *Box {
var box: *Box = undefined;
var box_index: ?BoxIndex = null;
var persistent: Box.Persistent = .{};
@ -1154,12 +995,21 @@ pub fn newBoxNoAppend(self: *UI, key: Key) *Box {
.index = box_index.?
};
return box;
}
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 newBox(self: *UI, key: Key) *Box {
const box = self.newBoxNoAppend(key);
self.appendBox(box);
return box;
}
@ -1206,7 +1056,7 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal {
while (event_index < self.events.len) {
var taken = false;
const event: Event = self.events.buffer[event_index];
const is_active = self.isKeyActive(key);
const is_active = self.isBoxActive(key);
if (event == .mouse_pressed and clickable and is_mouse_inside) {
const mouse_button = event.mouse_pressed;
@ -1269,11 +1119,7 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal {
return result;
}
pub fn isBoxHot(self: *UI, box: *Box) bool {
return self.isKeyHot(box.key);
}
pub fn isKeyHot(self: *UI, key: Key) bool {
fn isBoxHot(self: *UI, key: Key) bool {
if (self.hot_box_key) |hot_box_key| {
return hot_box_key.eql(key);
} else {
@ -1281,7 +1127,7 @@ pub fn isKeyHot(self: *UI, key: Key) bool {
}
}
pub fn isKeyActive(self: *UI, key: Key) bool {
fn isBoxActive(self: *UI, key: Key) bool {
inline for (std.meta.fields(rl.MouseButton)) |mouse_button_field| {
const mouse_button: rl.MouseButton = @enumFromInt(mouse_button_field.value);
@ -1349,7 +1195,6 @@ pub fn pushScrollbar(self: *UI, key: UI.Key) *Box {
self.pushParent(container);
const content_area = self.newBoxFromString("Scroll area");
content_area.flags.insert(.skip_draw);
content_area.flags.insert(.scrollable);
content_area.size = .{
.x = UI.Size.percent(1, 0),
@ -1370,62 +1215,58 @@ pub fn popScrollbar(self: *UI) void {
const content_area = self.getParent().?;
self.popParent(); // pop scroll area
const scrollbar_area = self.newBoxFromString("Scrollbar area");
scrollbar_area.background = rl.Color.gold;
scrollbar_area.flags.insert(.scrollable);
scrollbar_area.size = .{
.x = UI.Size.pixels(24, 1),
.y = UI.Size.percent(1, 0),
};
self.pushParent(scrollbar_area);
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);
if (used_content_size != 0) {
content_area.flags.remove(.skip_draw);
const draggable = self.newBoxFromString("Scrollbar button");
draggable.background = rl.Color.dark_brown;
draggable.flags.insert(.clickable);
draggable.flags.insert(.draggable_y);
draggable.size = .{
.x = UI.Size.percent(1, 1),
.y = UI.Size.percent(visible_percent, 1),
};
const sroll_offset = &content_area.persistent.sroll_offset;
const scrollbar_height = scrollbar_area.persistent.size.y;
const max_offset = scrollbar_height * (1 - visible_percent);
draggable.setFixedY(scrollbar_area.persistent.position.y + sroll_offset.* * max_offset);
const draggable_signal = self.signalFromBox(draggable);
if (draggable_signal.dragged()) {
sroll_offset.* += draggable_signal.drag.y / max_offset;
}
if (!content_area.flags.contains(.skip_draw)) {
const scrollbar_area = self.newBoxFromString("Scrollbar area");
scrollbar_area.background = rl.Color.gold;
scrollbar_area.flags.insert(.scrollable);
scrollbar_area.size = .{
.x = UI.Size.pixels(24, 1),
.y = UI.Size.percent(1, 0),
};
self.pushParent(scrollbar_area);
defer self.popParent();
const draggable = self.newBoxFromString("Scrollbar button");
draggable.background = rl.Color.dark_brown;
draggable.flags.insert(.clickable);
draggable.flags.insert(.draggable_y);
draggable.size = .{
.x = UI.Size.percent(1, 1),
.y = UI.Size.percent(visible_percent, 1),
};
const sroll_offset = &content_area.persistent.sroll_offset;
const scrollbar_height = content_area.persistent.size.y;
const max_offset = scrollbar_height * (1 - visible_percent);
draggable.setFixedY(content_area.persistent.position.y + sroll_offset.* * max_offset);
const draggable_signal = self.signalFromBox(draggable);
if (draggable_signal.dragged()) {
sroll_offset.* += draggable_signal.drag.y / max_offset;
}
const scroll_speed = 16;
const scrollbar_signal = self.signalFromBox(scrollbar_area);
if (scrollbar_signal.scrolled()) {
sroll_offset.* -= scrollbar_signal.scroll.y / max_offset * scroll_speed;
}
const content_area_signal = self.signalFromBox(content_area);
if (content_area_signal.scrolled()) {
sroll_offset.* -= content_area_signal.scroll.y / max_offset * scroll_speed;
}
sroll_offset.* = clamp(sroll_offset.*, 0, 1);
const scroll_speed = 16;
const scrollbar_signal = self.signalFromBox(scrollbar_area);
if (scrollbar_signal.scrolled()) {
sroll_offset.* -= scrollbar_signal.scroll.y / max_offset * scroll_speed;
}
const content_area_signal = self.signalFromBox(content_area);
if (content_area_signal.scrolled()) {
sroll_offset.* -= content_area_signal.scroll.y / max_offset * scroll_speed;
}
sroll_offset.* = clamp(sroll_offset.*, 0, 1);
self.popParent(); // pop container
}
pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *Box {
const box = self.clickableBox(text);
const box = self.newBoxFromString(text);
box.flags.insert(.clickable);
box.size.x = UI.Size.text(1, 1);
box.size.y = UI.Size.text(0.5, 1);
box.setText(font, text);
@ -1433,64 +1274,7 @@ pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *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 {
const box = self.newBox(UI.Key.initNil());
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();
}

View File

@ -1,54 +0,0 @@
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);
}