add protocol setting

This commit is contained in:
Rokas Puzonas 2025-05-27 10:07:19 +03:00
parent 0c237eb053
commit d49dc387fb
6 changed files with 470 additions and 269 deletions

View File

@ -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 = .{

View File

@ -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 {

View File

@ -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 {

View File

@ -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) {

View 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;

View File

@ -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 = &amplitude
},
};
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);
}
}
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();
}
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()});
}
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
});
const select_signal = ui.signal(select);
if (select_signal.clicked()) {
select.persistent.open = !select.persistent.open;
}
_ = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initGrowFull(),
});
{
const row = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
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(),
.align_x = .end
.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
});
row.beginChildren();
defer row.endChildren();
popup.setFloatPosition(0, select.persistent.size.y);
popup.beginChildren();
defer popup.endChildren();
defer _ = ui.signal(popup);
const btn = ui.textButton("Confirm");
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;
}
}
{ // 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 });
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 = &amplitude
// },
// };
// 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();
// }
}