From c588956226101d10888c1a921ab2170eb5717174 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Tue, 8 Apr 2025 21:36:10 +0300 Subject: [PATCH] add preview for sample generation --- src/app.zig | 25 +++++++---- src/screens/main_screen.zig | 82 +++++++++++++++++++++++++++---------- src/ui.zig | 13 ++++++ 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/src/app.zig b/src/app.zig index acf697e..16726d3 100644 --- a/src/app.zig +++ b/src/app.zig @@ -42,7 +42,7 @@ const FileChannel = struct { } }; -const DeviceChannel = struct { +pub const DeviceChannel = struct { const ChannelName = std.BoundedArray(u8, NIDaq.max_channel_name_size + 1); // +1 for null byte const Direction = enum { input, output }; @@ -76,6 +76,20 @@ const DeviceChannel = struct { pub fn getChannelName(self: *DeviceChannel) [:0]const u8 { return utils.getBoundedStringZ(&self.channel_name); } + + pub fn generateSine(samples: *std.ArrayList(f64), sample_rate: f64, frequency: f64, amplitude: f64) !void { + samples.clearRetainingCapacity(); + + const sample_count: usize = @intFromFloat(@ceil(sample_rate)); + assert(sample_count >= 1); + try samples.ensureTotalCapacity(sample_count); + + for (0..sample_count) |i| { + const i_f64: f64 = @floatFromInt(i); + const sample = std.math.sin(i_f64 / sample_rate * frequency * std.math.pi * 2) * amplitude; + samples.appendAssumeCapacity(sample); + } + } }; pub const ChannelView = struct { @@ -445,7 +459,7 @@ pub fn activateDeviceChannel(self: *App, channel_view: *ChannelView) !void { const task = try ni_daq.createTask(null); errdefer task.clear(); - const sample_rate = device_channel.max_sample_rate; + const sample_rate = channel_view.sample_rate.?; try task.createAIVoltageChannel(.{ .channel = device_channel.getChannelName(), .min_value = device_channel.min_value, @@ -468,13 +482,10 @@ pub fn activateDeviceChannel(self: *App, channel_view: *ChannelView) !void { const task = try ni_daq.createTask(null); errdefer task.clear(); - device_channel.write_pattern.clearAndFree(); - try device_channel.write_pattern.appendNTimes(2, 1000); - const write_pattern = device_channel.write_pattern.items; const samples_per_channel: u32 = @intCast(write_pattern.len); - const sample_rate = 5000; // device_channel.max_sample_rate; + const sample_rate = channel_view.sample_rate.?; try task.createAOVoltageChannel(.{ .channel = device_channel.getChannelName(), .min_value = device_channel.min_value, @@ -648,7 +659,7 @@ pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void { .x_range = RangeF64.init(0, 0), .y_range = RangeF64.init(max_value, min_value), .source = .{ .device = device_channel_index }, - .sample_rate = max_sample_rate, + .sample_rate = @min(max_sample_rate, 5000), .unit = unit }); errdefer _ = self.channel_views.pop(); diff --git a/src/screens/main_screen.zig b/src/screens/main_screen.zig index 47b2d19..fe9bac8 100644 --- a/src/screens/main_screen.zig +++ b/src/screens/main_screen.zig @@ -47,6 +47,9 @@ protocol_modal: ?*ChannelView = null, frequency_input: UI.TextInputStorage, amplitude_input: UI.TextInputStorage, protocol_error_message: ?[]const u8 = null, +protocol_graph_cache: Graph.Cache = .{}, +preview_samples: std.ArrayList(f64), +preview_samples_y_range: RangeF64 = RangeF64.init(0, 0), pub fn init(app: *App) !MainScreen { const allocator = app.allocator; @@ -54,10 +57,11 @@ pub fn init(app: *App) !MainScreen { var self = MainScreen{ .app = app, .frequency_input = UI.TextInputStorage.init(allocator), - .amplitude_input = UI.TextInputStorage.init(allocator) + .amplitude_input = UI.TextInputStorage.init(allocator), + .preview_samples = std.ArrayList(f64).init(allocator) }; - try self.frequency_input.setText("1000"); + try self.frequency_input.setText("10"); try self.amplitude_input.setText("10"); return self; @@ -66,6 +70,7 @@ pub fn init(app: *App) !MainScreen { pub fn deinit(self: *MainScreen) void { self.frequency_input.deinit(); self.amplitude_input.deinit(); + self.preview_samples.deinit(); self.clearProtocolErrorMessage(); } @@ -697,6 +702,7 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz fn openProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { self.protocol_modal = channel_view; + self.protocol_graph_cache.deinit(); } fn closeModal(self: *MainScreen) void { @@ -706,6 +712,8 @@ fn closeModal(self: *MainScreen) void { pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { var ui = &self.app.ui; + const device_channel = self.app.getChannelSourceDevice(channel_view).?; + const container = ui.createBox(.{ .key = ui.keyFromString("Protocol modal"), .background = srcery.black, @@ -719,6 +727,25 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { container.beginChildren(); defer container.endChildren(); + { + 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 }) + }); + + const samples = self.preview_samples.items; + const view_rect = Graph.ViewOptions{ + .x_range = RangeF64.init(0, @floatFromInt(samples.len)), + .y_range = self.preview_samples_y_range + }; + Graph.drawCached(&self.protocol_graph_cache, protocol_view.persistent.size, view_rect, samples); + if (self.protocol_graph_cache.texture) |texture| { + protocol_view.texture = texture.texture; + } + } + const FormInput = struct { name: []const u8, storage: *UI.TextInputStorage, @@ -740,6 +767,7 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { .value = &litude }, }; + var any_input_modified = false; for (form_inputs) |form_input| { const label = form_input.name; @@ -760,6 +788,35 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { label_box.alignment.y = .center; try ui.textInput(ui.keyFromString("Text input"), 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) { + try App.DeviceChannel.generateSine(&self.preview_samples, channel_view.sample_rate.?, frequency, amplitude); + self.preview_samples_y_range = RangeF64.init(-amplitude*1.1, amplitude*1.1); + self.protocol_graph_cache.invalidate(); } if (self.protocol_error_message) |message| { @@ -792,26 +849,9 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 }); if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) { - 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) { + try App.DeviceChannel.generateSine(&device_channel.write_pattern, channel_view.sample_rate.?, frequency, amplitude); + self.app.deferred_actions.appendAssumeCapacity(.{ .activate = channel_view }); diff --git a/src/ui.zig b/src/ui.zig index ecca840..0ac175e 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -2014,6 +2014,7 @@ pub fn isCtrlDown(self: *UI) bool { pub const TextInputStorage = struct { buffer: std.ArrayList(u8), + modified: bool = false, editing: bool = false, last_pressed_at_ns: i128 = 0, cursor_start: usize = 0, @@ -2032,11 +2033,15 @@ pub const TextInputStorage = struct { } pub fn setText(self: *TextInputStorage, text: []const u8) !void { + self.modified = true; + self.buffer.clearAndFree(); try self.buffer.appendSlice(text); } pub fn insertSingle(self: *TextInputStorage, index: usize, symbol: u8) !void { + self.modified = true; + try self.buffer.insert(index, symbol); if (self.cursor_start >= index) { @@ -2058,6 +2063,8 @@ pub const TextInputStorage = struct { const to_clamped = std.math.clamp(to , 0, self.buffer.items.len); if (from_clamped == to_clamped) return; + self.modified = true; + const lower = @min(from_clamped, to_clamped); const upper = @max(from_clamped, to_clamped); assert(lower < upper); @@ -2172,6 +2179,8 @@ pub const TextInputStorage = struct { fn insertMany(self: *TextInputStorage, index: usize, text: []const u8) !void { if (index > self.buffer.items.len) return; + self.modified = true; + try self.buffer.insertSlice(index, text); if (self.cursor_start >= index) { @@ -2184,6 +2193,10 @@ pub const TextInputStorage = struct { } }; +pub const TextInputResult = struct { + changed: bool = false +}; + pub fn mouseTooltip(self: *UI) *Box { const tooltip = self.getBoxByKey(mouse_tooltip_box_key).?;