diff --git a/build.zig.zon b/build.zig.zon index 7a16772..a3750b5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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 = .{ diff --git a/src/app.zig b/src/app.zig index 1803f64..a981848 100644 --- a/src/app.zig +++ b/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 { diff --git a/src/components/systems/view_controls.zig b/src/components/systems/view_controls.zig index 063ccf9..80256dc 100644 --- a/src/components/systems/view_controls.zig +++ b/src/components/systems/view_controls.zig @@ -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 { diff --git a/src/components/view.zig b/src/components/view.zig index 99b700d..424544a 100644 --- a/src/components/view.zig +++ b/src/components/view.zig @@ -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) { diff --git a/src/screens/channel_from_device.zig b/src/screens/channel_from_device.zig index 99c4e82..194ffb1 100644 --- a/src/screens/channel_from_device.zig +++ b/src/screens/channel_from_device.zig @@ -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; diff --git a/src/screens/main_screen.zig b/src/screens/main_screen.zig index c59c17c..f4b9c94 100644 --- a/src/screens/main_screen.zig +++ b/src/screens/main_screen.zig @@ -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(); + // } } \ No newline at end of file