add a rudimentary "add from device" window
This commit is contained in:
parent
a87a9c104e
commit
65ad8d7786
@ -2,4 +2,8 @@
|
||||
|
||||
```shell
|
||||
zig build run
|
||||
```
|
||||
```
|
||||
|
||||
## 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.
|
197
src/app.zig
197
src/app.zig
@ -15,6 +15,8 @@ const clamp = std.math.clamp;
|
||||
|
||||
const App = @This();
|
||||
|
||||
const max_channels = 64;
|
||||
|
||||
const Channel = struct {
|
||||
view_cache: Graph.Cache = .{},
|
||||
view_rect: Graph.ViewOptions,
|
||||
@ -31,7 +33,7 @@ const Channel = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
ui: UI,
|
||||
channels: std.BoundedArray(Channel, 64) = .{},
|
||||
channels: std.BoundedArray(Channel, max_channels) = .{},
|
||||
ni_daq: NIDaq,
|
||||
|
||||
shown_window: enum {
|
||||
@ -39,6 +41,11 @@ shown_window: enum {
|
||||
add_from_device
|
||||
} = .channels,
|
||||
|
||||
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(allocator: std.mem.Allocator) !App {
|
||||
return App{
|
||||
.allocator = allocator,
|
||||
@ -51,7 +58,7 @@ pub fn init(allocator: std.mem.Allocator) !App {
|
||||
.max_counter_inputs = 8,
|
||||
.max_analog_input_voltage_ranges = 4,
|
||||
.max_analog_output_voltage_ranges = 4
|
||||
}),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
@ -64,6 +71,11 @@ pub fn deinit(self: *App) void {
|
||||
channel.view_cache.deinit();
|
||||
}
|
||||
|
||||
for (self.selected_channels.constSlice()) |channel| {
|
||||
self.allocator.free(channel);
|
||||
}
|
||||
self.selected_channels.len = 0;
|
||||
|
||||
self.ui.deinit();
|
||||
}
|
||||
|
||||
@ -280,9 +292,182 @@ fn showChannelsWindow(self: *App) void {
|
||||
}
|
||||
}
|
||||
|
||||
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 names = try self.ni_daq.listDeviceNames();
|
||||
_ = names;
|
||||
const window = self.ui.newBoxFromString("Device window");
|
||||
window.size.x = UI.Size.percent(1, 0);
|
||||
window.size.y = UI.Size.percent(1, 0);
|
||||
window.layout_axis = .X;
|
||||
self.ui.pushParent(window);
|
||||
defer self.ui.popParent();
|
||||
|
||||
{
|
||||
const filters_box = self.ui.newBoxFromString("Filters box");
|
||||
filters_box.size.x = UI.Size.percent(0.5, 1);
|
||||
filters_box.size.y = UI.Size.percent(1, 0);
|
||||
filters_box.layout_axis = .Y;
|
||||
self.ui.pushParent(filters_box);
|
||||
defer self.ui.popParent();
|
||||
|
||||
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()) {
|
||||
self.device_filter = try NIDaq.BoundedDeviceName.fromSlice(device);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
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");
|
||||
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.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");
|
||||
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.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);
|
||||
add_button.setText(.text, "Add selected");
|
||||
const signal = self.ui.signalFromBox(add_button);
|
||||
if (signal.clicked()) {
|
||||
const selected_devices = self.selected_channels.constSlice();
|
||||
std.debug.print("{s}\n", .{selected_devices});
|
||||
|
||||
for (self.selected_channels.constSlice()) |channel| {
|
||||
self.allocator.free(channel);
|
||||
}
|
||||
self.selected_channels.len = 0;
|
||||
|
||||
self.shown_window = .channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const channels_box = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels list"));
|
||||
defer self.ui.popScrollbar();
|
||||
channels_box.layout_axis = .Y;
|
||||
channels_box.size.x = UI.Size.percent(1, 0);
|
||||
|
||||
var devices: []const [:0]const u8 = &.{};
|
||||
if (self.device_filter.len > 0) {
|
||||
devices = &.{
|
||||
self.device_filter.buffer[0..self.device_filter.len :0]
|
||||
};
|
||||
} else {
|
||||
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 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 self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
|
||||
ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device);
|
||||
}
|
||||
}
|
||||
|
||||
inline for (.{ ai_voltage_physical_channels, ao_physical_channels }) |channels| {
|
||||
for (channels) |channel| {
|
||||
const selected_channels_slice = self.selected_channels.constSlice();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const signal = self.ui.signalFromBox(channel_box);
|
||||
if (signal.clicked()) {
|
||||
if (findChannelIndexByName(selected_channels_slice, channel)) |index| {
|
||||
self.allocator.free(self.selected_channels.swapRemove(index));
|
||||
} else {
|
||||
self.selected_channels.appendAssumeCapacity(try self.allocator.dupeZ(u8, channel));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (self.selected_device.len > 0) {
|
||||
// const device: [:0]u8 = self.selected_device.buffer[0..self.selected_device.len :0];
|
||||
|
||||
// var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
|
||||
// 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 (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
|
||||
// ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device);
|
||||
// }
|
||||
|
||||
// const channels_box = self.ui.newBoxFromString("Channel names");
|
||||
// channels_box.size.x = UI.Size.percent(0.5, 0);
|
||||
// channels_box.size.y = UI.Size.percent(1, 0);
|
||||
// channels_box.layout_axis = .Y;
|
||||
// self.ui.pushParent(channels_box);
|
||||
// defer self.ui.popParent();
|
||||
|
||||
// for (ai_voltage_physical_channels) |channel_name| {
|
||||
// const channel_box = self.ui.newBoxFromString(channel_name);
|
||||
// channel_box.flags.insert(.clickable);
|
||||
// channel_box.size.x = UI.Size.text(1, 0);
|
||||
// channel_box.size.y = UI.Size.text(1, 0);
|
||||
// channel_box.setText(.text, channel_name);
|
||||
|
||||
// const signal = self.ui.signalFromBox(channel_box);
|
||||
// if (signal.clicked()) {
|
||||
// self.selected_device = try NIDaq.BoundedDeviceName.fromSlice(device);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
fn showToolbar(self: *App) void {
|
||||
@ -305,7 +490,7 @@ fn showToolbar(self: *App) void {
|
||||
.x = UI.Size.text(2, 1),
|
||||
.y = UI.Size.percent(1, 1)
|
||||
};
|
||||
box.setText("Add from file", .text);
|
||||
box.setText(.text, "Add from file",);
|
||||
|
||||
const signal = self.ui.signalFromBox(box);
|
||||
if (signal.clicked()) {
|
||||
@ -329,7 +514,7 @@ fn showToolbar(self: *App) void {
|
||||
.x = UI.Size.text(2, 1),
|
||||
.y = UI.Size.percent(1, 1)
|
||||
};
|
||||
box.setText("Add from device", .text);
|
||||
box.setText(.text, "Add from device");
|
||||
|
||||
const signal = self.ui.signalFromBox(box);
|
||||
if (signal.clicked()) {
|
||||
|
@ -11,7 +11,16 @@ const log = std.log.scoped(.ni_daq);
|
||||
const max_device_name_size = 255;
|
||||
const max_task_name_size = 255;
|
||||
|
||||
pub const BoundedDeviceName = std.BoundedArray(u8, max_device_name_size);
|
||||
const max_channel_name_size = count: {
|
||||
var count: u32 = 0;
|
||||
count += max_device_name_size;
|
||||
count += 1; // '/'
|
||||
count += 2; // 'ai' or 'ao' or 'co' or 'ci'
|
||||
count += 3; // '0' -> '999', I can't imagine this counter being over 1000. What device has over 1000 channels????
|
||||
break :count count;
|
||||
};
|
||||
|
||||
pub const BoundedDeviceName = std.BoundedArray(u8, max_device_name_size + 1); // +1 for null byte
|
||||
const StringArrayListUnmanaged = std.ArrayListUnmanaged([:0]const u8);
|
||||
|
||||
const NIDaq = @This();
|
||||
@ -276,17 +285,7 @@ const DeviceBuffers = struct {
|
||||
array_list: StringArrayListUnmanaged,
|
||||
|
||||
fn init(allocator: std.mem.Allocator, capacity: u32) !ChannelNames {
|
||||
const max_channel_name_size = count: {
|
||||
var count: u32 = 0;
|
||||
count += max_device_name_size;
|
||||
count += 1; // '/'
|
||||
count += 2; // 'ai' or 'ao' or 'co' or 'ci'
|
||||
count += std.math.log10_int(capacity) + 1;
|
||||
|
||||
break :count count;
|
||||
};
|
||||
|
||||
const buffer_size = capacity * (max_channel_name_size + 2);
|
||||
const buffer_size = capacity * (max_channel_name_size + 2); // +2 for ', ' separator
|
||||
|
||||
const buffer = try allocator.alloc(u8, buffer_size);
|
||||
errdefer allocator.free(buffer);
|
||||
@ -434,13 +433,7 @@ pub fn logDAQmxError(error_code: i32) void {
|
||||
|
||||
var msg: [512:0]u8 = .{ 0 } ** 512;
|
||||
if (c.DAQmxGetErrorString(error_code, &msg, msg.len) == 0) {
|
||||
if (error_code < 0) {
|
||||
log.err("DAQmx ({}): {s}", .{error_code, msg});
|
||||
} else if (!std.mem.startsWith(u8, &msg, "Error code could not be found.")) {
|
||||
// Ignore positive error codes if it could not be found.
|
||||
// This commonly happens when trying to preallocate bytes for buffer and it returns a positive number.
|
||||
log.warn("DAQmx ({}): {s}", .{error_code, msg});
|
||||
}
|
||||
log.err("DAQmx ({}): {s}", .{error_code, msg});
|
||||
} else {
|
||||
log.err("DAQmx ({}): Unknown (Buffer too small for message)", .{error_code});
|
||||
}
|
||||
@ -454,6 +447,14 @@ pub fn checkDAQmxError(error_code: i32, err: anyerror) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checkDAQmxErrorIgnoreWarnings(error_code: i32, err: anyerror) !void {
|
||||
if (error_code > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try checkDAQmxError(error_code, err);
|
||||
}
|
||||
|
||||
fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), buffer: []u8) !void {
|
||||
const count = std.mem.count(u8, buffer, ",") + 1;
|
||||
if (count > array_list.capacity) {
|
||||
@ -504,6 +505,7 @@ pub fn listDeviceNames(self: *NIDaq) ![]const [:0]const u8 {
|
||||
self.clearAllDeviceBuffers();
|
||||
|
||||
const required_size = c.DAQmxGetSysDevNames(null, 0);
|
||||
try checkDAQmxErrorIgnoreWarnings(required_size, error.DAQmxGetSysDevNames);
|
||||
if (required_size == 0) {
|
||||
return self.device_names.items;
|
||||
}
|
||||
@ -560,7 +562,7 @@ fn listDevicePhysicalChannels(
|
||||
array_list.clearRetainingCapacity();
|
||||
|
||||
const required_size = getPhysicalChannels(device, null, 0);
|
||||
try checkDAQmxError(required_size, error.GetPhysicalChannels);
|
||||
try checkDAQmxErrorIgnoreWarnings(required_size, error.GetPhysicalChannels);
|
||||
if (required_size == 0) {
|
||||
return array_list.items;
|
||||
}
|
||||
@ -710,7 +712,7 @@ fn listDeviceVoltageRanges(
|
||||
voltage_ranges.clearRetainingCapacity();
|
||||
|
||||
const count = getVoltageRanges(device, null, 0);
|
||||
try checkDAQmxError(count, error.GetVoltageRanges);
|
||||
try checkDAQmxErrorIgnoreWarnings(count, error.GetVoltageRanges);
|
||||
if (count == 0) {
|
||||
return voltage_ranges.items;
|
||||
}
|
||||
@ -771,7 +773,7 @@ pub fn listDeviceAIMeasurementTypes(self: NIDaq, device: [:0]const u8) !AIMeasur
|
||||
_ = self;
|
||||
|
||||
const count = c.DAQmxGetDevAISupportedMeasTypes(device, null, 0);
|
||||
try checkDAQmxError(count, error.DAQmxGetDevAISupportedMeasTypes);
|
||||
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAISupportedMeasTypes);
|
||||
assert(count <= result.buffer.len);
|
||||
|
||||
try checkDAQmxError(
|
||||
@ -794,7 +796,7 @@ pub fn listDeviceAOOutputTypes(self: NIDaq, device: [:0]const u8) !AOOutputTypeL
|
||||
_ = self;
|
||||
|
||||
const count = c.DAQmxGetDevAOSupportedOutputTypes(device, null, 0);
|
||||
try checkDAQmxError(count, error.DAQmxGetDevAOSupportedOutputTypes);
|
||||
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAOSupportedOutputTypes);
|
||||
assert(count <= result.buffer.len);
|
||||
|
||||
try checkDAQmxError(
|
||||
|
69
src/ui.zig
69
src/ui.zig
@ -287,13 +287,20 @@ pub const Box = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setText(self: *Box, text: []const u8, font: Assets.FontId) void {
|
||||
pub fn setText(self: *Box, font: Assets.FontId, text: []const u8) void {
|
||||
self.text = .{
|
||||
.content = self.allocator.dupe(u8, text) catch return,
|
||||
.font = font
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setFixedX(self: *Box, x: f32) void {
|
||||
self.flags.insert(.fixed_x);
|
||||
self.fixed_rect.x = x;
|
||||
@ -452,6 +459,19 @@ pub fn begin(self: *UI) void {
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
var i: usize = 0;
|
||||
while (i < self.boxes.len) {
|
||||
const box = &self.boxes.buffer[i];
|
||||
if (box.last_used_frame != self.frame_index) {
|
||||
_ = self.boxes.swapRemove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
self.frame_index += 1;
|
||||
_ = self.frameArena().reset(.retain_capacity);
|
||||
self.parent_index_stack.len = 0;
|
||||
@ -489,19 +509,6 @@ pub fn end(self: *UI) void {
|
||||
self.popParent();
|
||||
assert(self.parent_index_stack.len == 0);
|
||||
|
||||
{
|
||||
var i: usize = 0;
|
||||
while (i < self.boxes.len) {
|
||||
const box = &self.boxes.buffer[i];
|
||||
if (box.last_used_frame != self.frame_index) {
|
||||
_ = self.boxes.swapRemove(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var active_box_flags = self.getActiveBoxFlags();
|
||||
var hover_box_flags: Box.Flags = .{};
|
||||
@ -921,12 +928,13 @@ pub fn newBox(self: *UI, key: Key) *Box {
|
||||
var box: *Box = undefined;
|
||||
var box_index: BoxIndex = undefined;
|
||||
var persistent: Box.Persistent = .{};
|
||||
if (self.findBoxByKey(key)) |found_box| {
|
||||
if (self.findBoxIndexByKey(key)) |found_box_index| {
|
||||
const found_box = &self.boxes.buffer[found_box_index];
|
||||
assert(found_box.last_used_frame < self.frame_index);
|
||||
|
||||
persistent = found_box.persistent;
|
||||
box = found_box;
|
||||
box_index = found_box.index;
|
||||
box_index = found_box_index;
|
||||
} else {
|
||||
box = self.boxes.addOneAssumeCapacity();
|
||||
box_index = self.boxes.len - 1;
|
||||
@ -941,8 +949,9 @@ pub fn newBox(self: *UI, key: Key) *Box {
|
||||
.index = box_index
|
||||
};
|
||||
|
||||
if (self.getParent()) |parent| {
|
||||
box.parent_index = parent.index;
|
||||
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];
|
||||
@ -958,15 +967,22 @@ pub fn newBox(self: *UI, key: Key) *Box {
|
||||
return box;
|
||||
}
|
||||
|
||||
fn findBoxByKey(self: *UI, key: Key) ?*Box {
|
||||
for (self.boxes.slice()) |*box| {
|
||||
fn findBoxIndexByKey(self: *UI, key: Key) ?BoxIndex {
|
||||
for (0.., self.boxes.slice()) |index, *box| {
|
||||
if (box.key.eql(key)) {
|
||||
return box;
|
||||
return @intCast(index);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn findBoxByKey(self: *UI, key: Key) ?*Box {
|
||||
if (self.findBoxIndexByKey(key)) |box_index| {
|
||||
return &self.boxes.buffer[box_index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn signalFromBox(self: *UI, box: *Box) Signal {
|
||||
var result = Signal{};
|
||||
|
||||
@ -1058,11 +1074,18 @@ fn isBoxActive(self: *UI, key: Key) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn getParent(self: *UI) ?*Box {
|
||||
fn getParentIndex(self: *UI) ?BoxIndex {
|
||||
const parent_stack: []BoxIndex = self.parent_index_stack.slice();
|
||||
|
||||
if (parent_stack.len > 0) {
|
||||
const parent_index = parent_stack[parent_stack.len - 1];
|
||||
return parent_stack[parent_stack.len - 1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getParent(self: *UI) ?*Box {
|
||||
if (self.getParentIndex()) |parent_index| {
|
||||
return &self.boxes.buffer[parent_index];
|
||||
} else {
|
||||
return null;
|
||||
|
Loading…
Reference in New Issue
Block a user