add protocol setting
This commit is contained in:
parent
0c237eb053
commit
d49dc387fb
@ -19,6 +19,10 @@
|
||||
.url = "git+https://github.com/lassade/profiler.zig.git#d066d066c36c4eebd494babf15c1cdbd2d512b12",
|
||||
.hash = "122097461acc2064f5f89b85d76d2a02232579864b17604617a333789c892f2d262f",
|
||||
},
|
||||
.@"zig-datetime" = .{
|
||||
.url = "git+https://github.com/frmdstryr/zig-datetime.git#70aebf28fb3e137cd84123a9349d157a74708721",
|
||||
.hash = "122077215ce36e125a490e59ec1748ffd4f6ba00d4d14f7308978e5360711d72d77f",
|
||||
},
|
||||
},
|
||||
|
||||
.paths = .{
|
||||
|
158
src/app.zig
158
src/app.zig
@ -676,8 +676,6 @@ pub const Channel = struct {
|
||||
collected_samples_id: Id,
|
||||
allowed_sample_values: ?RangeF64 = null,
|
||||
allowed_sample_rates: ?RangeF64 = null,
|
||||
write_pattern: std.ArrayListUnmanaged(f64) = .{},
|
||||
output_task: ?NIDaq.Task = null,
|
||||
|
||||
pub fn deinit(self: *Channel, allocator: Allocator) void {
|
||||
self.clear(allocator);
|
||||
@ -686,12 +684,7 @@ pub const Channel = struct {
|
||||
pub fn clear(self: *Channel, allocator: Allocator) void {
|
||||
self.allowed_sample_rates = null;
|
||||
self.allowed_sample_values = null;
|
||||
|
||||
self.write_pattern.clearAndFree(allocator);
|
||||
if (self.output_task) |task| {
|
||||
task.clear();
|
||||
self.output_task = null;
|
||||
}
|
||||
_ = allocator;
|
||||
}
|
||||
|
||||
pub fn generateSine(
|
||||
@ -786,6 +779,16 @@ pub const Project = struct {
|
||||
sample: u64
|
||||
};
|
||||
|
||||
pub const ProtocolEvent = struct {
|
||||
filename: []u8,
|
||||
startedAt: f64,
|
||||
stoppedAt: ?f64 = null,
|
||||
|
||||
pub fn deinit(self: ProtocolEvent, allocator: Allocator) void {
|
||||
allocator.free(self.filename);
|
||||
}
|
||||
};
|
||||
|
||||
const file_format_version: u8 = 0;
|
||||
const file_endian = std.builtin.Endian.big;
|
||||
|
||||
@ -796,6 +799,7 @@ pub const Project = struct {
|
||||
statistic_points: std.ArrayListUnmanaged(u64) = .{},
|
||||
export_location: ?[]u8 = null,
|
||||
|
||||
protocol_events: std.BoundedArray(ProtocolEvent, 32) = .{},
|
||||
solutions: std.ArrayListUnmanaged(Solution) = .{},
|
||||
gain_changes: Transform.GainChanges = .{},
|
||||
|
||||
@ -889,6 +893,25 @@ pub const Project = struct {
|
||||
self.sample_lists.remove(sample_list_id);
|
||||
}
|
||||
|
||||
pub fn readF64SliceFromFile(self: *Project, allocator: Allocator, file: std.fs.File) ![]f64 {
|
||||
_ = self;
|
||||
|
||||
var samples = std.ArrayList(f64).init(allocator);
|
||||
errdefer samples.deinit();
|
||||
|
||||
while (true) {
|
||||
try samples.ensureUnusedCapacity(2048);
|
||||
|
||||
const count = try file.readAll(std.mem.sliceAsBytes(samples.unusedCapacitySlice()));
|
||||
if (count == 0) break;
|
||||
assert(count % @sizeOf(f64) == 0);
|
||||
|
||||
samples.items.len += @divExact(count, @sizeOf(f64));
|
||||
}
|
||||
|
||||
return try samples.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn readSamplesFromFile(self: *Project, allocator: Allocator, sample_list_id: Id, file: std.fs.File, sample_count: usize) !void {
|
||||
var bytes_left: usize = sample_count * 8;
|
||||
var buffer: [SampleList.Block.capacity * 8]u8 = undefined;
|
||||
@ -1002,6 +1025,10 @@ pub const Project = struct {
|
||||
|
||||
self.statistic_points.deinit(allocator);
|
||||
self.experiment_name.deinit(allocator);
|
||||
|
||||
for (self.protocol_events.constSlice()) |event| {
|
||||
event.deinit(allocator);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getViewTransform(self: *Project, view_id: Id) Transform {
|
||||
@ -1305,8 +1332,8 @@ pub const Command = union(enum) {
|
||||
start_collection,
|
||||
stop_collection,
|
||||
export_project,
|
||||
stop_output: Id, // Channel id
|
||||
start_output: Id, // Channel id
|
||||
// stop_output: Id, // Channel id
|
||||
// start_output: Id, // Channel id
|
||||
add_file_from_picker,
|
||||
reload_file: Id, // File id
|
||||
};
|
||||
@ -1450,6 +1477,7 @@ screen: enum {
|
||||
main_screen: MainScreen,
|
||||
channel_from_device: ChannelFromDeviceScreen,
|
||||
project: Project,
|
||||
output_task: ?NIDaq.Task = null,
|
||||
|
||||
collection_mutex: std.Thread.Mutex = .{},
|
||||
collection_samples_mutex: std.Thread.Mutex = .{},
|
||||
@ -1517,6 +1545,11 @@ pub fn deinit(self: *App) void {
|
||||
self.collection_condition.signal();
|
||||
self.collection_thread.join();
|
||||
|
||||
if (self.output_task) |task| {
|
||||
task.clear();
|
||||
self.output_task = null;
|
||||
}
|
||||
|
||||
self.deinitUI();
|
||||
|
||||
if (self.ni_daq) |*ni_daq| {
|
||||
@ -1713,6 +1746,21 @@ fn exportProject(self: *App) !void {
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
{ // Protocols during experiment
|
||||
try writer.writeAll("Protocols during experiment\n");
|
||||
try writer.writeAll("Start Stop File\n");
|
||||
for (self.project.protocol_events.constSlice()) |event| {
|
||||
const started_at = event.startedAt / sample_rate;
|
||||
const stopped_at = (event.stoppedAt orelse @as(f64, @floatFromInt(project.getSampleTimestamp() orelse 0))) / sample_rate;
|
||||
|
||||
const line = try std.fmt.allocPrint(self.allocator, "{d:.2} {d:.2} {s}\n", .{started_at, stopped_at, event.filename});
|
||||
defer self.allocator.free(line);
|
||||
|
||||
try writer.writeAll(line);
|
||||
}
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
{ // Pipete solutions
|
||||
try writer.writeAll("Pipete Solution:\n");
|
||||
try writer.writeAll(self.project.pipete_solution.items);
|
||||
@ -1726,7 +1774,7 @@ fn exportProject(self: *App) !void {
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
{
|
||||
{ // Channel .bin files
|
||||
var view_iter = project.views.iterator();
|
||||
while (view_iter.next()) |view| {
|
||||
if (view.reference != .channel) {
|
||||
@ -1890,14 +1938,14 @@ pub fn tick(self: *App) !void {
|
||||
log.err("Failed to add file from picker: {}", .{e});
|
||||
};
|
||||
},
|
||||
.start_output => |channel_id| {
|
||||
self.startOutput(channel_id) catch |e| {
|
||||
log.err("Failed to start output on channel: {}", .{e});
|
||||
};
|
||||
},
|
||||
.stop_output => |channel_id| {
|
||||
self.stopOutput(channel_id);
|
||||
},
|
||||
// .start_output => |channel_id| {
|
||||
// self.startOutput(channel_id) catch |e| {
|
||||
// log.err("Failed to start output on channel: {}", .{e});
|
||||
// };
|
||||
// },
|
||||
// .stop_output => |channel_id| {
|
||||
// self.stopOutput(channel_id);
|
||||
// },
|
||||
.reload_file => |file_id| {
|
||||
self.loadFile(file_id) catch |e| {
|
||||
log.err("Failed to load file: {}", .{ e });
|
||||
@ -2064,39 +2112,58 @@ pub fn stopCollection(self: *App) void {
|
||||
self.collection_mutex.lock();
|
||||
defer self.collection_mutex.unlock();
|
||||
|
||||
self.stopOutput();
|
||||
|
||||
var collection_task = self.collection_task.?;
|
||||
collection_task.deinit(self.allocator);
|
||||
|
||||
self.collection_task = null;
|
||||
}
|
||||
|
||||
fn startOutput(self: *App, channel_id: Id) !void {
|
||||
pub fn startOutput(self: *App, channel_name: []const u8, protocol_filename: []const u8) !void {
|
||||
const ni_daq = &(self.ni_daq orelse return error.MissingNiDaq);
|
||||
const channel = self.getChannel(channel_id) orelse return error.ChannelNotFound;
|
||||
const channel_name = utils.getBoundedStringZ(&channel.name);
|
||||
const allowed_sample_values = channel.allowed_sample_values orelse return error.NoAllowedSampleValues;
|
||||
const sample_rate = self.project.getSampleRate() orelse return error.MissingSampleRate;
|
||||
|
||||
if (channel.output_task != null) {
|
||||
if (self.output_task != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cwd = std.fs.cwd();
|
||||
|
||||
const protocol_file = try cwd.openFile(protocol_filename, .{});
|
||||
defer protocol_file.close();
|
||||
|
||||
const samples = try self.project.readF64SliceFromFile(self.allocator, protocol_file);
|
||||
defer self.allocator.free(samples);
|
||||
|
||||
const task = try ni_daq.createTask(null);
|
||||
errdefer task.clear();
|
||||
|
||||
const device_name = NIDaq.getDeviceNameFromChannel(channel_name) orelse unreachable;
|
||||
var device_name_z = try utils.initBoundedStringZ(Channel.Device, device_name);
|
||||
var channel_name_z = try utils.initBoundedStringZ(Channel.Name, channel_name);
|
||||
|
||||
const allowed_sample_values = try ni_daq.listDeviceAOVoltageRanges(utils.getBoundedStringZ(&device_name_z));
|
||||
|
||||
if (allowed_sample_values.len == 0) {
|
||||
return error.NoRangesFound;
|
||||
} else if (allowed_sample_values.len > 1) {
|
||||
log.warn("Multiple sample value ranges found, picking first option", .{});
|
||||
}
|
||||
|
||||
try task.createAOVoltageChannel(.{
|
||||
.channel = channel_name,
|
||||
.min_value = allowed_sample_values.lower,
|
||||
.max_value = allowed_sample_values.upper
|
||||
.channel = utils.getBoundedStringZ(&channel_name_z),
|
||||
.min_value = allowed_sample_values[0].low,
|
||||
.max_value = allowed_sample_values[0].high
|
||||
});
|
||||
|
||||
try task.setContinousSampleRate(.{
|
||||
.sample_rate = sample_rate
|
||||
});
|
||||
|
||||
const samples_per_channel: u32 = @intCast(channel.write_pattern.items.len);
|
||||
const samples_per_channel: u32 = @intCast(samples.len);
|
||||
const written = try task.writeAnalog(.{
|
||||
.write_array = channel.write_pattern.items,
|
||||
.write_array = samples,
|
||||
.samples_per_channel = samples_per_channel,
|
||||
.timeout = -1
|
||||
});
|
||||
@ -2106,20 +2173,26 @@ fn startOutput(self: *App, channel_id: Id) !void {
|
||||
|
||||
try task.start();
|
||||
|
||||
channel.output_task = task;
|
||||
self.output_task = task;
|
||||
|
||||
try self.project.protocol_events.append(.{
|
||||
.filename = try self.allocator.dupe(u8, protocol_filename),
|
||||
.startedAt = @floatFromInt(self.project.getSampleTimestamp() orelse 0)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stopOutput(self: *App, channel_id: Id) void {
|
||||
const channel = self.getChannel(channel_id) orelse return;
|
||||
|
||||
if (channel.output_task == null) {
|
||||
pub fn stopOutput(self: *App) void {
|
||||
if (self.output_task == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const output_task = channel.output_task.?;
|
||||
const output_task = self.output_task.?;
|
||||
output_task.clear();
|
||||
|
||||
channel.output_task = null;
|
||||
self.output_task = null;
|
||||
|
||||
const protocol_events: []Project.ProtocolEvent = self.project.protocol_events.slice();
|
||||
protocol_events[protocol_events.len - 1].stoppedAt = @floatFromInt(self.project.getSampleTimestamp() orelse 0);
|
||||
}
|
||||
|
||||
pub fn isCollectionInProgress(self: *App) bool {
|
||||
@ -2127,20 +2200,11 @@ pub fn isCollectionInProgress(self: *App) bool {
|
||||
}
|
||||
|
||||
pub fn isOutputingInProgress(self: *App) bool {
|
||||
var channel_iter = self.project.channels.idIterator();
|
||||
while (channel_iter.next()) |channel_id| {
|
||||
if (self.isChannelOutputing(channel_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return self.output_task != null;
|
||||
}
|
||||
|
||||
pub fn stopAllOutput(self: *App) void {
|
||||
var channel_iter = self.project.channels.idIterator();
|
||||
while (channel_iter.next()) |channel_id| {
|
||||
self.stopOutput(channel_id);
|
||||
}
|
||||
self.stopOutput();
|
||||
}
|
||||
|
||||
fn isNiDaqInUse(self: *App) bool {
|
||||
|
@ -161,7 +161,7 @@ undo_stack: CommandFrameArray = .{},
|
||||
last_applied_command: usize = 0,
|
||||
zoom_start: ?ViewAxisPosition = null,
|
||||
cursor: ?ViewAxisPosition = null,
|
||||
view_protocol_modal: ?Id = null, // View id
|
||||
// view_protocol_modal: ?Id = null, // View id
|
||||
// selected_tool: enum { move, select, marker } = .move,
|
||||
|
||||
pub fn init(app: *App) System {
|
||||
|
@ -167,7 +167,7 @@ fn showToolbar(ctx: Context, view_id: Id) void {
|
||||
const channel_id = view.reference.channel;
|
||||
const channel = ctx.app.getChannel(channel_id).?;
|
||||
const channel_name = utils.getBoundedStringZ(&channel.name);
|
||||
const channel_type = NIDaq.getChannelType(channel_name).?;
|
||||
// const channel_type = NIDaq.getChannelType(channel_name).?;
|
||||
|
||||
{
|
||||
const follow = ui.textButton("Follow");
|
||||
@ -183,35 +183,35 @@ fn showToolbar(ctx: Context, view_id: Id) void {
|
||||
}
|
||||
}
|
||||
|
||||
if (channel_type == .analog_output) {
|
||||
const button = ui.button(ui.keyFromString("Output generation"));
|
||||
button.texture = Assets.output_generation;
|
||||
button.size.y = UI.Sizing.initGrowFull();
|
||||
// if (channel_type == .analog_output) {
|
||||
// const button = ui.button(ui.keyFromString("Output generation"));
|
||||
// button.texture = Assets.output_generation;
|
||||
// button.size.y = UI.Sizing.initGrowFull();
|
||||
|
||||
const signal = ui.signal(button);
|
||||
if (signal.clicked()) {
|
||||
if (ctx.app.isChannelOutputing(channel_id)) {
|
||||
ctx.app.pushCommand(.{
|
||||
.stop_output = channel_id
|
||||
});
|
||||
} else {
|
||||
ctx.view_controls.view_protocol_modal = view_id;
|
||||
}
|
||||
}
|
||||
// const signal = ui.signal(button);
|
||||
// if (signal.clicked()) {
|
||||
// if (ctx.app.isChannelOutputing(channel_id)) {
|
||||
// ctx.app.pushCommand(.{
|
||||
// .stop_output = channel_id
|
||||
// });
|
||||
// } else {
|
||||
// ctx.view_controls.view_protocol_modal = view_id;
|
||||
// }
|
||||
// }
|
||||
|
||||
var color = rl.Color.white;
|
||||
if (ctx.app.isChannelOutputing(channel_id)) {
|
||||
color = srcery.red;
|
||||
}
|
||||
// var color = rl.Color.white;
|
||||
// if (ctx.app.isChannelOutputing(channel_id)) {
|
||||
// color = srcery.red;
|
||||
// }
|
||||
|
||||
if (signal.active) {
|
||||
button.texture_color = color.alpha(0.6);
|
||||
} else if (signal.hot) {
|
||||
button.texture_color = color.alpha(0.8);
|
||||
} else {
|
||||
button.texture_color = color;
|
||||
}
|
||||
}
|
||||
// if (signal.active) {
|
||||
// button.texture_color = color.alpha(0.6);
|
||||
// } else if (signal.hot) {
|
||||
// button.texture_color = color.alpha(0.8);
|
||||
// } else {
|
||||
// button.texture_color = color;
|
||||
// }
|
||||
// }
|
||||
|
||||
view_name = channel_name;
|
||||
} else if (view.reference == .file) {
|
||||
|
@ -97,12 +97,12 @@ pub fn tick(self: *Screen) !void {
|
||||
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);
|
||||
}
|
||||
// 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, ao_physical_channels }) |channels| {
|
||||
inline for (.{ ai_voltage_physical_channels }) |channels| {
|
||||
for (channels) |channel| {
|
||||
const channel_button = ui.textButton(channel);
|
||||
channel_button.background = srcery.black;
|
||||
|
@ -39,17 +39,21 @@ side_panel: union(enum) {
|
||||
} = .project,
|
||||
|
||||
modal: ?union(enum){
|
||||
view_protocol: Id, // View id
|
||||
notes
|
||||
notes,
|
||||
add_proprotional,
|
||||
protocol
|
||||
} = null,
|
||||
|
||||
// Protocol modal
|
||||
frequency_input: UI.TextInputStorage,
|
||||
amplitude_input: UI.TextInputStorage,
|
||||
protocol_error_message: ?[]const u8 = null,
|
||||
protocol_graph_cache: Graph.RenderCache = .{},
|
||||
preview_sample_list_id: App.Id,
|
||||
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
|
||||
protocol_filename: ?[]u8 = null,
|
||||
protocol_file_picker: ?Platform.FilePickerId = null,
|
||||
protocol_channel: std.BoundedArray(u8, 128) = .{},
|
||||
// frequency_input: UI.TextInputStorage,
|
||||
// amplitude_input: UI.TextInputStorage,
|
||||
// protocol_error_message: ?[]const u8 = null,
|
||||
// protocol_graph_cache: Graph.RenderCache = .{},
|
||||
// preview_sample_list_id: App.Id,
|
||||
// preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
|
||||
|
||||
// Notes modal
|
||||
notes_storage: UI.TextInputStorage,
|
||||
@ -72,10 +76,10 @@ file_save_file_picker: ?Platform.FilePickerId = null,
|
||||
pub fn init(app: *App) !MainScreen {
|
||||
const allocator = app.allocator;
|
||||
|
||||
var self = MainScreen{
|
||||
const self = MainScreen{
|
||||
.app = app,
|
||||
.frequency_input = UI.TextInputStorage.init(allocator),
|
||||
.amplitude_input = UI.TextInputStorage.init(allocator),
|
||||
// .frequency_input = UI.TextInputStorage.init(allocator),
|
||||
// .amplitude_input = UI.TextInputStorage.init(allocator),
|
||||
.sample_rate_input = UI.TextInputStorage.init(allocator),
|
||||
.notes_storage = UI.TextInputStorage.init(allocator),
|
||||
.solution_input = UI.TextInputStorage.init(allocator),
|
||||
@ -84,18 +88,18 @@ pub fn init(app: *App) !MainScreen {
|
||||
.view_name_input = UI.TextInputStorage.init(allocator),
|
||||
.gain_input = UI.TextInputStorage.init(allocator),
|
||||
.view_controls = ViewControlsSystem.init(app),
|
||||
.preview_sample_list_id = try app.project.addSampleList(allocator)
|
||||
// .preview_sample_list_id = try app.project.addSampleList(allocator)
|
||||
};
|
||||
|
||||
try self.frequency_input.setText("10");
|
||||
try self.amplitude_input.setText("10");
|
||||
// try self.frequency_input.setText("10");
|
||||
// try self.amplitude_input.setText("10");
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *MainScreen) void {
|
||||
self.frequency_input.deinit();
|
||||
self.amplitude_input.deinit();
|
||||
// self.frequency_input.deinit();
|
||||
// self.amplitude_input.deinit();
|
||||
self.sample_rate_input.deinit();
|
||||
self.notes_storage.deinit();
|
||||
self.solution_input.deinit();
|
||||
@ -103,20 +107,22 @@ pub fn deinit(self: *MainScreen) void {
|
||||
self.pipete_solution.deinit();
|
||||
self.view_name_input.deinit();
|
||||
self.gain_input.deinit();
|
||||
self.app.project.removeSampleList(self.preview_sample_list_id);
|
||||
// self.app.project.removeSampleList(self.preview_sample_list_id);
|
||||
|
||||
self.clearProtocolErrorMessage();
|
||||
// self.clearProtocolErrorMessage();
|
||||
|
||||
if (self.protocol_filename) |str| {
|
||||
self.app.allocator.free(str);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn showProtocolModal(self: *MainScreen, view_id: Id) !void {
|
||||
pub fn showProtocolModal(self: *MainScreen) !void {
|
||||
var ui = &self.app.ui;
|
||||
const allocator = self.app.allocator;
|
||||
const ni_daq = &(self.app.ni_daq orelse return);
|
||||
// const allocator = self.app.allocator;
|
||||
|
||||
const view = self.app.getView(view_id) orelse return;
|
||||
if (view.reference != .channel) return;
|
||||
const channel_id = view.reference.channel;
|
||||
const channel = self.app.getChannel(channel_id) orelse return;
|
||||
const sample_rate = self.app.project.getSampleRate() orelse return;
|
||||
// const sample_rate = self.app.project.getSampleRate() orelse return;
|
||||
// _ = sample_rate;
|
||||
|
||||
const container = ui.createBox(.{
|
||||
.key = ui.keyFromString("Protocol modal"),
|
||||
@ -130,151 +136,245 @@ pub fn showProtocolModal(self: *MainScreen, view_id: Id) !void {
|
||||
});
|
||||
container.beginChildren();
|
||||
defer container.endChildren();
|
||||
defer _ = ui.signal(container);
|
||||
|
||||
{
|
||||
const protocol_view = ui.createBox(.{
|
||||
.key = ui.keyFromString("Protocol view"),
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initFixedPixels(ui.rem(4)),
|
||||
.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 })
|
||||
});
|
||||
|
||||
if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
|
||||
const view_rect = Graph.ViewOptions{
|
||||
.x_range = RangeF64.init(0, @floatFromInt(sample_list.getLength())),
|
||||
.y_range = self.preview_samples_y_range
|
||||
};
|
||||
Graph.drawCached(&self.protocol_graph_cache, protocol_view.persistent.size, view_rect, sample_list);
|
||||
if (self.protocol_graph_cache.texture) |texture| {
|
||||
protocol_view.texture = texture.texture;
|
||||
}
|
||||
if (ui.fileInput(.{
|
||||
.key = ui.keyFromString("Protocol filename"),
|
||||
.allocator = self.app.allocator,
|
||||
.path = self.protocol_filename,
|
||||
.file_picker = &self.protocol_file_picker
|
||||
})) |filename| {
|
||||
if (self.protocol_filename) |str| {
|
||||
self.app.allocator.free(str);
|
||||
}
|
||||
self.protocol_filename = filename;
|
||||
}
|
||||
|
||||
const FormInput = struct {
|
||||
name: []const u8,
|
||||
storage: *UI.TextInputStorage,
|
||||
value: *f32
|
||||
};
|
||||
{ // Select channel
|
||||
var available_channels = std.ArrayList([]const u8).init(self.app.allocator);
|
||||
defer available_channels.deinit();
|
||||
|
||||
var frequency: f32 = 0;
|
||||
var amplitude: f32 = 0;
|
||||
|
||||
const form_inputs = &[_]FormInput{
|
||||
.{
|
||||
.name = "Frequency",
|
||||
.storage = &self.frequency_input,
|
||||
.value = &frequency
|
||||
},
|
||||
.{
|
||||
.name = "Amplitude",
|
||||
.storage = &self.amplitude_input,
|
||||
.value = &litude
|
||||
},
|
||||
};
|
||||
var any_input_modified = false;
|
||||
|
||||
for (form_inputs) |form_input| {
|
||||
const label = form_input.name;
|
||||
const text_input_storage = form_input.storage;
|
||||
|
||||
const row = ui.createBox(.{
|
||||
.key = ui.keyFromString(label),
|
||||
.layout_direction = .left_to_right,
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initFixedPixels(ui.rem(1.5))
|
||||
});
|
||||
row.beginChildren();
|
||||
defer row.endChildren();
|
||||
|
||||
const label_box = ui.label("{s}", .{ label });
|
||||
label_box.size.x = UI.Sizing.initFixed(.{ .font_size = 5 });
|
||||
label_box.size.y = UI.Sizing.initGrowFull();
|
||||
label_box.alignment.y = .center;
|
||||
|
||||
try ui.textInput(.{
|
||||
.key = ui.keyFromString("Text input"),
|
||||
.storage = text_input_storage
|
||||
});
|
||||
|
||||
any_input_modified = any_input_modified or text_input_storage.modified;
|
||||
}
|
||||
|
||||
if (any_input_modified) {
|
||||
self.clearProtocolErrorMessage();
|
||||
|
||||
for (form_inputs) |form_input| {
|
||||
const label = form_input.name;
|
||||
const text_input_storage: *UI.TextInputStorage = form_input.storage;
|
||||
|
||||
const number = std.fmt.parseFloat(f32, text_input_storage.buffer.items) catch {
|
||||
try self.setProtocolErrorMessage("ERROR: {s} must be a number", .{ label });
|
||||
continue;
|
||||
};
|
||||
|
||||
if (number <= 0) {
|
||||
try self.setProtocolErrorMessage("ERROR: {s} must be positive", .{ label });
|
||||
const devices = try ni_daq.listDeviceNames();
|
||||
for (devices) |device| {
|
||||
if (!try ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
form_input.value.* = number;
|
||||
for (try ni_daq.listDeviceAOPhysicalChannels(device)) |channel| {
|
||||
try available_channels.append(channel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const select = ui.button(ui.keyFromString("Protocol channel"));
|
||||
select.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
|
||||
if (self.protocol_channel.len == 0) {
|
||||
select.setText("Channel: -");
|
||||
} else {
|
||||
select.setFmtText("Channel: {s}", .{self.protocol_channel.constSlice()});
|
||||
}
|
||||
|
||||
const select_signal = ui.signal(select);
|
||||
|
||||
if (select_signal.clicked()) {
|
||||
select.persistent.open = !select.persistent.open;
|
||||
}
|
||||
|
||||
if (select.persistent.open) {
|
||||
const popup = ui.createBox(.{
|
||||
.key = ui.keyFromString("Channel popup"),
|
||||
.size_x = UI.Sizing.initFixedPixels(ui.rem(10)),
|
||||
.size_y = UI.Sizing.initFitChildren(),
|
||||
.flags = &.{ .clickable, .scrollable },
|
||||
.layout_direction = .top_to_bottom,
|
||||
.float_relative_to = select,
|
||||
.background = srcery.black,
|
||||
.borders = UI.Borders.all(.{ .color = srcery.bright_black, .size = 4 }),
|
||||
.draw_on_top = true
|
||||
});
|
||||
popup.setFloatPosition(0, select.persistent.size.y);
|
||||
popup.beginChildren();
|
||||
defer popup.endChildren();
|
||||
defer _ = ui.signal(popup);
|
||||
|
||||
for (available_channels.items) |channel| {
|
||||
const select_option = ui.textButton(channel);
|
||||
select_option.alignment.x = .start;
|
||||
select_option.size.x = UI.Sizing.initGrowFull();
|
||||
select_option.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 });
|
||||
select_option.background = srcery.black;
|
||||
|
||||
const signal = ui.signal(select_option);
|
||||
if (signal.clicked()) {
|
||||
select.persistent.open = false;
|
||||
self.protocol_channel.len = 0;
|
||||
try self.protocol_channel.appendSlice(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (select_signal.clicked_outside) {
|
||||
select.persistent.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.protocol_error_message == null and any_input_modified) {
|
||||
if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
|
||||
var preview_samples: std.ArrayListUnmanaged(f64) = .{};
|
||||
defer preview_samples.deinit(allocator);
|
||||
{ // Start
|
||||
var btn = ui.textButton("Start");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 4 });
|
||||
if (self.protocol_filename != null and self.protocol_channel.len > 0) {
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
|
||||
|
||||
try App.Channel.generateSine(&preview_samples, allocator, sample_rate, frequency, amplitude);
|
||||
sample_list.clear(allocator);
|
||||
try sample_list.append(preview_samples.items);
|
||||
|
||||
self.preview_samples_y_range = RangeF64.init(-amplitude*1.1, amplitude*1.1);
|
||||
self.protocol_graph_cache.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
if (self.protocol_error_message) |message| {
|
||||
_ = ui.createBox(.{
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initFixedText(),
|
||||
.text_color = srcery.red,
|
||||
.align_x = .start,
|
||||
.align_y = .start,
|
||||
.flags = &.{ .wrap_text },
|
||||
.text = message
|
||||
});
|
||||
}
|
||||
|
||||
_ = ui.createBox(.{
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initGrowFull(),
|
||||
});
|
||||
|
||||
{
|
||||
const row = ui.createBox(.{
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initFitChildren(),
|
||||
.align_x = .end
|
||||
});
|
||||
row.beginChildren();
|
||||
defer row.endChildren();
|
||||
|
||||
const btn = ui.textButton("Confirm");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
|
||||
|
||||
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) {
|
||||
if (self.protocol_error_message == null) {
|
||||
try App.Channel.generateSine(&channel.write_pattern, allocator, sample_rate, frequency, amplitude);
|
||||
|
||||
self.app.pushCommand(.{ .start_output = channel_id });
|
||||
self.view_controls.view_protocol_modal = null;
|
||||
if (ui.signal(btn).clicked()) {
|
||||
try self.app.startOutput(self.protocol_channel.constSlice(), self.protocol_filename.?);
|
||||
self.modal = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = ui.signal(container);
|
||||
// {
|
||||
// const protocol_view = ui.createBox(.{
|
||||
// .key = ui.keyFromString("Protocol view"),
|
||||
// .size_x = UI.Sizing.initGrowFull(),
|
||||
// .size_y = UI.Sizing.initFixedPixels(ui.rem(4)),
|
||||
// .borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 })
|
||||
// });
|
||||
|
||||
// if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
|
||||
// const view_rect = Graph.ViewOptions{
|
||||
// .x_range = RangeF64.init(0, @floatFromInt(sample_list.getLength())),
|
||||
// .y_range = self.preview_samples_y_range
|
||||
// };
|
||||
// Graph.drawCached(&self.protocol_graph_cache, protocol_view.persistent.size, view_rect, sample_list);
|
||||
// if (self.protocol_graph_cache.texture) |texture| {
|
||||
// protocol_view.texture = texture.texture;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// const FormInput = struct {
|
||||
// name: []const u8,
|
||||
// storage: *UI.TextInputStorage,
|
||||
// value: *f32
|
||||
// };
|
||||
|
||||
// var frequency: f32 = 0;
|
||||
// var amplitude: f32 = 0;
|
||||
|
||||
// const form_inputs = &[_]FormInput{
|
||||
// .{
|
||||
// .name = "Frequency",
|
||||
// .storage = &self.frequency_input,
|
||||
// .value = &frequency
|
||||
// },
|
||||
// .{
|
||||
// .name = "Amplitude",
|
||||
// .storage = &self.amplitude_input,
|
||||
// .value = &litude
|
||||
// },
|
||||
// };
|
||||
// var any_input_modified = false;
|
||||
|
||||
// for (form_inputs) |form_input| {
|
||||
// const label = form_input.name;
|
||||
// const text_input_storage = form_input.storage;
|
||||
|
||||
// const row = ui.createBox(.{
|
||||
// .key = ui.keyFromString(label),
|
||||
// .layout_direction = .left_to_right,
|
||||
// .size_x = UI.Sizing.initGrowFull(),
|
||||
// .size_y = UI.Sizing.initFixedPixels(ui.rem(1.5))
|
||||
// });
|
||||
// row.beginChildren();
|
||||
// defer row.endChildren();
|
||||
|
||||
// const label_box = ui.label("{s}", .{ label });
|
||||
// label_box.size.x = UI.Sizing.initFixed(.{ .font_size = 5 });
|
||||
// label_box.size.y = UI.Sizing.initGrowFull();
|
||||
// label_box.alignment.y = .center;
|
||||
|
||||
// try ui.textInput(.{
|
||||
// .key = ui.keyFromString("Text input"),
|
||||
// .storage = text_input_storage
|
||||
// });
|
||||
|
||||
// any_input_modified = any_input_modified or text_input_storage.modified;
|
||||
// }
|
||||
|
||||
// if (any_input_modified) {
|
||||
// self.clearProtocolErrorMessage();
|
||||
|
||||
// for (form_inputs) |form_input| {
|
||||
// const label = form_input.name;
|
||||
// const text_input_storage: *UI.TextInputStorage = form_input.storage;
|
||||
|
||||
// const number = std.fmt.parseFloat(f32, text_input_storage.buffer.items) catch {
|
||||
// try self.setProtocolErrorMessage("ERROR: {s} must be a number", .{ label });
|
||||
// continue;
|
||||
// };
|
||||
|
||||
// if (number <= 0) {
|
||||
// try self.setProtocolErrorMessage("ERROR: {s} must be positive", .{ label });
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// form_input.value.* = number;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (self.protocol_error_message == null and any_input_modified) {
|
||||
// if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
|
||||
// var preview_samples: std.ArrayListUnmanaged(f64) = .{};
|
||||
// defer preview_samples.deinit(allocator);
|
||||
|
||||
// try App.Channel.generateSine(&preview_samples, allocator, sample_rate, frequency, amplitude);
|
||||
// sample_list.clear(allocator);
|
||||
// try sample_list.append(preview_samples.items);
|
||||
|
||||
// self.preview_samples_y_range = RangeF64.init(-amplitude*1.1, amplitude*1.1);
|
||||
// self.protocol_graph_cache.invalidate();
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (self.protocol_error_message) |message| {
|
||||
// _ = ui.createBox(.{
|
||||
// .size_x = UI.Sizing.initGrowFull(),
|
||||
// .size_y = UI.Sizing.initFixedText(),
|
||||
// .text_color = srcery.red,
|
||||
// .align_x = .start,
|
||||
// .align_y = .start,
|
||||
// .flags = &.{ .wrap_text },
|
||||
// .text = message
|
||||
// });
|
||||
// }
|
||||
|
||||
// _ = ui.createBox(.{
|
||||
// .size_x = UI.Sizing.initGrowFull(),
|
||||
// .size_y = UI.Sizing.initGrowFull(),
|
||||
// });
|
||||
|
||||
// {
|
||||
// const row = ui.createBox(.{
|
||||
// .size_x = UI.Sizing.initGrowFull(),
|
||||
// .size_y = UI.Sizing.initFitChildren(),
|
||||
// .align_x = .end
|
||||
// });
|
||||
// row.beginChildren();
|
||||
// defer row.endChildren();
|
||||
|
||||
// const btn = ui.textButton("Confirm");
|
||||
// btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
|
||||
|
||||
// if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) {
|
||||
// if (self.protocol_error_message == null) {
|
||||
// try App.Channel.generateSine(&channel.write_pattern, allocator, sample_rate, frequency, amplitude);
|
||||
|
||||
// self.app.pushCommand(.{ .start_output = channel_id });
|
||||
// self.view_controls.view_protocol_modal = null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
fn showNotesModal(self: *MainScreen) !void {
|
||||
@ -315,21 +415,39 @@ fn showNotesModal(self: *MainScreen) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn setProtocolErrorMessage(self: *MainScreen, comptime fmt: []const u8, args: anytype) !void {
|
||||
self.clearProtocolErrorMessage();
|
||||
fn showProportionalAdd(self: *MainScreen) !void {
|
||||
var ui = &self.app.ui;
|
||||
|
||||
const allocator = self.app.allocator;
|
||||
self.protocol_error_message = try std.fmt.allocPrint(allocator, fmt, args);
|
||||
const container = ui.createBox(.{
|
||||
.key = ui.keyFromString("Proportional add modal"),
|
||||
.background = srcery.black,
|
||||
.size_x = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
|
||||
.size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
|
||||
.layout_direction = .top_to_bottom,
|
||||
.padding = UI.Padding.all(ui.rem(1.5)),
|
||||
.flags = &.{ .clickable },
|
||||
.layout_gap = ui.rem(0.5)
|
||||
});
|
||||
container.beginChildren();
|
||||
defer container.endChildren();
|
||||
defer _ = ui.signal(container);
|
||||
}
|
||||
|
||||
fn clearProtocolErrorMessage(self: *MainScreen) void {
|
||||
const allocator = self.app.allocator;
|
||||
// fn setProtocolErrorMessage(self: *MainScreen, comptime fmt: []const u8, args: anytype) !void {
|
||||
// self.clearProtocolErrorMessage();
|
||||
|
||||
if (self.protocol_error_message) |msg| {
|
||||
allocator.free(msg);
|
||||
}
|
||||
self.protocol_error_message = null;
|
||||
}
|
||||
// const allocator = self.app.allocator;
|
||||
// self.protocol_error_message = try std.fmt.allocPrint(allocator, fmt, args);
|
||||
// }
|
||||
|
||||
// fn clearProtocolErrorMessage(self: *MainScreen) void {
|
||||
// const allocator = self.app.allocator;
|
||||
|
||||
// if (self.protocol_error_message) |msg| {
|
||||
// allocator.free(msg);
|
||||
// }
|
||||
// self.protocol_error_message = null;
|
||||
// }
|
||||
|
||||
fn showProjectSettings(self: *MainScreen) !void {
|
||||
var ui = &self.app.ui;
|
||||
@ -955,6 +1073,17 @@ fn showToolbar(self: *MainScreen) void {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const select_btn = ui.textButton("... proportional");
|
||||
select_btn.alignment.x = .start;
|
||||
select_btn.size.x = UI.Sizing.initGrowFull();
|
||||
select_btn.background = srcery.hard_black;
|
||||
select_btn.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 });
|
||||
if (ui.signal(select_btn).clicked()) {
|
||||
self.app.main_screen.modal = .add_proprotional;
|
||||
}
|
||||
}
|
||||
|
||||
// inline for (options) |option| {
|
||||
// const select_option = ui.textButton(option[1]);
|
||||
// select_option.alignment.x = .start;
|
||||
@ -982,18 +1111,28 @@ fn showToolbar(self: *MainScreen) void {
|
||||
}
|
||||
}
|
||||
|
||||
_ = ui.createBox(.{ .size_x = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
{
|
||||
var btn = ui.textButton("Protocol");
|
||||
btn.borders = UI.Borders.all(.{ .size = 4, .color = srcery.hard_black });
|
||||
|
||||
// {
|
||||
// var btn = ui.textButton("Move");
|
||||
// if (self.view_controls.selected_tool == .move) {
|
||||
// btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
||||
// }
|
||||
if (self.app.isCollectionInProgress()) {
|
||||
const is_outputing = self.app.isOutputingInProgress();
|
||||
|
||||
// if (ui.signal(btn).clicked()) {
|
||||
// self.view_controls.selected_tool = .move;
|
||||
// }
|
||||
// }
|
||||
if (is_outputing) {
|
||||
btn.borders = UI.Borders.all(.{ .size = 4, .color = srcery.red });
|
||||
} else {
|
||||
btn.borders = UI.Borders.all(.{ .size = 4, .color = srcery.green });
|
||||
}
|
||||
|
||||
if (ui.signal(btn).clicked()) {
|
||||
if (is_outputing) {
|
||||
self.app.stopAllOutput();
|
||||
} else {
|
||||
self.app.main_screen.modal = .protocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// var btn = ui.textButton("Select");
|
||||
@ -1231,14 +1370,7 @@ pub fn tick(self: *MainScreen) !void {
|
||||
const root = ui.parentBox().?;
|
||||
root.layout_direction = .top_to_bottom;
|
||||
|
||||
if (self.view_controls.view_protocol_modal) |view_protocol_modal| {
|
||||
if (self.modal == null) {
|
||||
self.modal = .{ .view_protocol = view_protocol_modal };
|
||||
}
|
||||
self.view_controls.view_protocol_modal = null;
|
||||
}
|
||||
|
||||
const was_modal_open = self.modal != null;
|
||||
// const was_modal_open = self.modal != null;
|
||||
|
||||
var maybe_modal_overlay: ?*UI.Box = null;
|
||||
if (self.modal != null) {
|
||||
@ -1262,8 +1394,9 @@ pub fn tick(self: *MainScreen) !void {
|
||||
defer modal_overlay.endChildren();
|
||||
|
||||
switch (self.modal.?) {
|
||||
.view_protocol => |view_id| try self.showProtocolModal(view_id),
|
||||
.notes => try self.showNotesModal()
|
||||
.notes => try self.showNotesModal(),
|
||||
.add_proprotional => try self.showProportionalAdd(),
|
||||
.protocol => try self.showProtocolModal()
|
||||
}
|
||||
|
||||
if (ui.signal(modal_overlay).clicked()) {
|
||||
@ -1319,8 +1452,8 @@ pub fn tick(self: *MainScreen) !void {
|
||||
}
|
||||
}
|
||||
|
||||
const is_modal_open = self.modal != null;
|
||||
if (!was_modal_open and is_modal_open) {
|
||||
self.protocol_graph_cache.clear();
|
||||
}
|
||||
// const is_modal_open = self.modal != null;
|
||||
// if (!was_modal_open and is_modal_open) {
|
||||
// self.protocol_graph_cache.clear();
|
||||
// }
|
||||
}
|
Loading…
Reference in New Issue
Block a user