const std = @import("std"); const App = @import("../app.zig"); const UI = @import("../ui.zig"); const srcery = @import("../srcery.zig"); const NIDaq = @import("../ni-daq/root.zig"); const assert = std.debug.assert; const log = std.log.scoped(.channel_from_device_screen); const Screen = @This(); app: *App, hot_channel: ?[:0]const u8 = null, // TODO: 32 limit selected_channels: std.BoundedArray([:0]u8, 32) = .{}, // TODO: Don't use arena channel_names: std.heap.ArenaAllocator, pub fn init(app: *App) Screen { return Screen{ .app = app, .channel_names = std.heap.ArenaAllocator.init(app.allocator) }; } pub fn deinit(self: *Screen) void { _ = self.channel_names.reset(.free_all); } fn isChannelSelected(self: *Screen, channel: []const u8) bool { for (self.selected_channels.slice()) |selected_channel| { if (std.mem.eql(u8, selected_channel, channel)) { return true; } } return false; } fn selectChannel(self: *Screen, channel: []const u8) void { if (self.selected_channels.unusedCapacitySlice().len == 0) { log.warn("Maximum number of selected channels reached", .{}); return; } if (self.isChannelSelected(channel)) { return; } const allocator = self.channel_names.allocator(); const channel_dupe = allocator.dupeZ(u8, channel) catch |e| { log.err("Failed to duplicate channel name: {}", .{e}); return; }; self.selected_channels.appendAssumeCapacity(channel_dupe); } fn deselectChannel(self: *Screen, channel: []const u8) void { for (0.., self.selected_channels.slice()) |i, selected_channel| { if (std.mem.eql(u8, selected_channel, channel)) { _ = self.selected_channels.swapRemove(i); return; } } } fn toggleChannel(self: *Screen, channel: []const u8) void { if (self.isChannelSelected(channel)) { self.deselectChannel(channel); } else { self.selectChannel(channel); } } pub fn tick(self: *Screen) !void { var ni_daq = self.app.ni_daq orelse return; var ui = &self.app.ui; if (ui.isKeyboardPressed(.key_escape)) { self.app.screen = .main; } const root = ui.parentBox().?; root.layout_direction = .left_to_right; { const panel = ui.beginScrollbar(ui.keyFromString("Channels")); defer ui.endScrollbar(); panel.layout_direction = .top_to_bottom; const devices = try ni_daq.listDeviceNames(); for (devices) |device| { var ai_voltage_physical_channels: []const [:0]const u8 = &.{}; if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) { ai_voltage_physical_channels = try ni_daq.listDeviceAIPhysicalChannels(device); } // var ao_physical_channels: []const [:0]const u8 = &.{}; // if (try ni_daq.checkDeviceAOOutputType(device, .Voltage)) { // ao_physical_channels = try ni_daq.listDeviceAOPhysicalChannels(device); // } inline for (.{ ai_voltage_physical_channels }) |channels| { for (channels) |channel| { const channel_button = ui.textButton(channel); channel_button.background = srcery.black; if (self.isChannelSelected(channel)) { channel_button.background = srcery.bright_white; channel_button.text_color = srcery.black; } if (self.app.getChannelByName(channel) != null) { channel_button.text_color = srcery.white; channel_button.background = srcery.hard_black; } else { const signal = ui.signal(channel_button); if (signal.clicked()) { self.toggleChannel(channel); } if (signal.hot) { self.hot_channel = channel; } } } } } } { const panel = ui.createBox(.{ .size_x = UI.Sizing.initGrowFull(), .size_y = UI.Sizing.initGrowFull(), .borders = .{ .left = .{ .color = srcery.hard_black, .size = 4 } }, .layout_direction = .top_to_bottom, .padding = UI.Padding.all(ui.rem(2)) }); panel.beginChildren(); defer panel.endChildren(); const info_container = ui.createBox(.{ .size_x = UI.Sizing.initGrowFull(), .size_y = UI.Sizing.initGrowFull(), .layout_direction = .top_to_bottom, }); if (self.hot_channel) |hot_channel| { info_container.beginChildren(); defer info_container.endChildren(); var maybe_hot_device: ?[:0]const u8 = null; var device_buff: NIDaq.BoundedDeviceName = .{}; if (NIDaq.getDeviceNameFromChannel(hot_channel)) |device| { device_buff.appendSliceAssumeCapacity(device); device_buff.buffer[device_buff.len] = 0; maybe_hot_device = device_buff.buffer[0..device_buff.len :0]; } var channel_type_name: []const u8 = "unknown"; if (NIDaq.getChannelType(hot_channel)) |channel_type| { channel_type_name = channel_type.name(); } { const channel_info = ui.createBox(.{ .size_x = UI.Sizing.initGrowFull(), .size_y = UI.Sizing.initFitChildren(), .padding = .{ .bottom = ui.rem(2) }, .layout_direction = .top_to_bottom }); channel_info.beginChildren(); defer channel_info.endChildren(); _ = ui.label("Channel properties", .{}); _ = ui.label("Name: {s}", .{hot_channel}); _ = ui.label("Type: {s}", .{channel_type_name}); } if (maybe_hot_device) |hot_device| { _ = ui.label("Device properties", .{}); if (ni_daq.listDeviceAIMeasurementTypes(hot_device)) |measurement_types| { _ = ui.label("Measurement types: {} types", .{measurement_types.len}); } else |e| { log.err("ni_daq.listDeviceAIMeasurementTypes(): {}", .{ e }); } } } else { info_container.alignment.x = .center; info_container.alignment.y = .center; info_container.setText("Hover on a channel"); info_container.flags.insert(.wrap_text); info_container.text_color = srcery.hard_black; info_container.font = .{ .variant = .bold_italic, .size = ui.rem(3) }; } const add_button = ui.button(ui.keyFromString("Add channels")); add_button.setFmtText("Add {} selected channels", .{self.selected_channels.len}); add_button.size.x = UI.Sizing.initGrowFull(); add_button.alignment.x = .center; if (self.selected_channels.len > 0) { add_button.borders = UI.Borders.all(.{ .color = srcery.green, .size = 2 }); const signal = ui.signal(add_button); if (signal.clicked()) { self.app.screen = .main; for (self.selected_channels.slice()) |channel| { _ = try self.app.addView(.{ .channel = try self.app.addChannel(channel) }); } _ = self.channel_names.reset(.free_all); self.selected_channels.len = 0; } } else { add_button.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 }); } } }