diff --git a/src/app.zig b/src/app.zig index a930064..f321a90 100644 --- a/src/app.zig +++ b/src/app.zig @@ -62,6 +62,7 @@ pub const ChannelView = struct { y_range: RangeF64, sample_rate: ?f64 = null, unit: ?NIDaq.Unit = null, + follow: bool = false, source: union(enum) { file: usize, @@ -338,6 +339,7 @@ pub fn startDeviceChannelReading(self: *App, channel_view: *ChannelView) void { }; device_channel.active_task = task; + channel_view.follow = true; } pub fn appendChannelFromFile(self: *App, path: []const u8) !void { diff --git a/src/graph.zig b/src/graph.zig index 74c5673..2194ac1 100644 --- a/src/graph.zig +++ b/src/graph.zig @@ -27,19 +27,24 @@ pub const ViewOptions = struct { }; pub const Cache = struct { + const Key = struct { + options: ViewOptions, + drawn_x_range: RangeF64 + }; + texture: ?rl.RenderTexture2D = null, - options: ?ViewOptions = null, + key: ?Key = null, pub fn deinit(self: *Cache) void { if (self.texture) |texture| { texture.unload(); self.texture = null; } - self.options = null; + self.key = null; } pub fn invalidate(self: *Cache) void { - self.options = null; + self.key = null; } pub fn draw(self: Cache, rect: rl.Rectangle) void { @@ -178,7 +183,7 @@ pub fn drawCached(cache: *Cache, render_size: Vec2, options: ViewOptions, sample if (texure.width != render_width or texure.height != render_height) { render_texture.unload(); cache.texture = null; - cache.options = null; + cache.key = null; } } @@ -191,12 +196,17 @@ pub fn drawCached(cache: *Cache, render_size: Vec2, options: ViewOptions, sample const render_texture = cache.texture.?; - if (cache.options != null and std.meta.eql(cache.options.?, options)) { + const cache_key = Cache.Key{ + .options = options, + .drawn_x_range = RangeF64.init(0, @max(@as(f64, @floatFromInt(samples.len)) - 1, 0)).intersectPositive(options.x_range) + }; + + if (cache.key != null and std.meta.eql(cache.key.?, cache_key)) { // Cached graph hasn't changed, no need to redraw. return; } - cache.options = options; + cache.key = cache_key; render_texture.begin(); defer render_texture.end(); diff --git a/src/ni-daq/task-pool.zig b/src/ni-daq/task-pool.zig index c8360c7..7517163 100644 --- a/src/ni-daq/task-pool.zig +++ b/src/ni-daq/task-pool.zig @@ -74,7 +74,6 @@ fn readAnalog(task_pool: *TaskPool, entry: *Entry, timeout: f64) !void { task_pool.mutex.lock(); defer task_pool.mutex.unlock(); - if (entry.sampling.sample_count) |sample_count| { try entry.samples.ensureTotalCapacity(sample_count); } else { diff --git a/src/screens/main_screen.zig b/src/screens/main_screen.zig index 8197f3b..f1550a3 100644 --- a/src/screens/main_screen.zig +++ b/src/screens/main_screen.zig @@ -76,6 +76,7 @@ fn pushChannelMoveCommand(self: *MainScreen, channel_view: *ChannelView, x_range view_rect.x_range = x_range; view_rect.y_range = y_range; + channel_view.follow = false; } fn pushChannelMoveCommandAxis(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis, view_range: RangeF64) void { @@ -100,6 +101,8 @@ fn showChannelViewGraph(self: *MainScreen, channel_view: *ChannelView) *UI.Box { .size_y = UI.Sizing.initGrowFull(), .background = srcery.black, .flags = &.{ .clickable, .draggable, .scrollable }, + .align_x = .center, + .align_y = .center, }); graph_box.beginChildren(); defer graph_box.endChildren(); @@ -154,6 +157,15 @@ fn showChannelViewGraph(self: *MainScreen, channel_view: *ChannelView) *UI.Box { graph_box.texture = texture.texture; } + if (view_rect.x_range.size() == 0 or view_rect.y_range.size() == 0) { + graph_box.setText(""); + graph_box.text_color = srcery.hard_black; + graph_box.font = .{ + .variant = .bold_italic, + .size = ui.rem(3) + }; + } + return graph_box; } @@ -513,6 +525,8 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Sizing) !void { var ui = &self.app.ui; + const show_ruler = true; + const channel_view_box = ui.createBox(.{ .key = UI.Key.initPtr(channel_view), .layout_direction = .top_to_bottom, @@ -522,7 +536,34 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz channel_view_box.beginChildren(); defer channel_view_box.endChildren(); - const show_ruler = true; + const toolbar = ui.createBox(.{ + .layout_direction = .left_to_right, + .layout_gap = 16, + .background = srcery.hard_black, + .size_x = UI.Sizing.initGrowFull(), + .size_y = UI.Sizing.initFixed(.{ .pixels = ui.rem(2) }) + }); + { + toolbar.beginChildren(); + defer toolbar.endChildren(); + + if (self.app.getChannelSourceDevice(channel_view)) |device_channel| { + _ = device_channel; + + const follow = ui.button("Follow"); + follow.background = srcery.hard_black; + if (channel_view.follow) { + follow.borders = UI.Borders.bottom(.{ + .color = srcery.green, + .size = 4 + }); + } + if (ui.signal(follow).clicked()) { + channel_view.follow = !channel_view.follow; + } + } + } + if (!show_ruler) { _ = self.showChannelViewGraph(channel_view); @@ -645,16 +686,18 @@ pub fn tick(self: *MainScreen) !void { if (self.app.started_collecting) { for (self.app.listChannelViews()) |*channel_view| { const device_channel = self.app.getChannelSourceDevice(channel_view) orelse continue; + if (!channel_view.follow) continue; const sample_rate = device_channel.active_task.?.sampling.sample_rate; - const samples = device_channel.samples.items; - const sample_count: f32 = @floatFromInt(samples.len); + const sample_count: f32 = @floatFromInt(device_channel.samples.items.len); + + channel_view.view_rect.y_range = channel_view.y_range; channel_view.view_rect.x_range.lower = 0; if (sample_count > channel_view.view_rect.x_range.upper) { channel_view.view_rect.x_range.upper = sample_count + @as(f32, @floatCast(sample_rate)) * 10; } - channel_view.view_cache.invalidate(); + // channel_view.view_cache.invalidate(); } } @@ -665,6 +708,7 @@ pub fn tick(self: *MainScreen) !void { const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels")); defer ui.endScrollbar(); scroll_area.layout_direction = .top_to_bottom; + scroll_area.layout_gap = 4; for (self.app.listChannelViews()) |*channel_view| { try self.showChannelView(channel_view, UI.Sizing.initFixed(.{ .pixels = channel_view.height })); diff --git a/src/ui.zig b/src/ui.zig index d885ec0..3a1b8bb 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -420,6 +420,12 @@ pub const Borders = struct { .right = border, }; } + + pub fn bottom(border: Border) Borders { + return Borders{ + .bottom = border, + }; + } }; const BoxIndex = std.math.IntFittingRange(0, max_boxes);