add gain change live updates
This commit is contained in:
parent
eafafbab89
commit
0edeaefbf0
581
src/app.zig
581
src/app.zig
@ -194,93 +194,125 @@ const SumRingBuffer = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Transform = union(enum) {
|
||||
sliding_window: f64,
|
||||
multiply: f64,
|
||||
addition: f64,
|
||||
|
||||
pub const State = struct {
|
||||
sum_ring_buffer: ?SumRingBuffer = null,
|
||||
|
||||
pub fn init(arena: Allocator, transform: Transform) !State {
|
||||
var sum_ring_buffer: ?SumRingBuffer = null;
|
||||
if (transform == .sliding_window) {
|
||||
const window_width: usize = @intFromFloat(@ceil(transform.sliding_window));
|
||||
sum_ring_buffer = SumRingBuffer.init(try arena.alloc(f64, window_width));
|
||||
}
|
||||
|
||||
return State{
|
||||
.sum_ring_buffer = sum_ring_buffer
|
||||
};
|
||||
}
|
||||
|
||||
pub fn prepare(self: *State, transform: Transform, source: *SampleList, from_block_index: SampleList.Block.Id) void {
|
||||
switch (transform) {
|
||||
.sliding_window => |window_width_f64| {
|
||||
const sum_ring_buffer = &(self.sum_ring_buffer.?);
|
||||
const window_width: usize = @intFromFloat(@ceil(window_width_f64));
|
||||
|
||||
|
||||
const window_width_blocks: usize = @intFromFloat(@ceil(@as(f64, @floatFromInt(window_width)) / SampleList.Block.capacity));
|
||||
|
||||
for (0..window_width_blocks) |block_offset| {
|
||||
const block_id = @as(isize, @intCast(from_block_index)) - @as(isize, @intCast(window_width_blocks - block_offset));
|
||||
if (block_id < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const source_block = source.getBlock(@intCast(block_id)).?;
|
||||
for (source_block.samplesSlice()) |sample| {
|
||||
sum_ring_buffer.append(sample);
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {}
|
||||
}
|
||||
}
|
||||
pub const Transform = struct {
|
||||
const max_gain_changes = 32;
|
||||
pub const GainChanges = std.BoundedArray(GainChange, max_gain_changes);
|
||||
pub const GainChange = struct {
|
||||
gain: f64,
|
||||
sample: f64
|
||||
};
|
||||
|
||||
pub fn apply(self: Transform, state: *State, destination: []f64, source: []f64) void {
|
||||
switch (self) {
|
||||
.sliding_window => {
|
||||
const sum_ring_buffer = &(state.sum_ring_buffer.?);
|
||||
gain_changes: GainChanges = .{},
|
||||
|
||||
if (sum_ring_buffer.len == 0) {
|
||||
@memcpy(destination[0..source.len], source);
|
||||
} else {
|
||||
for (0..source.len) |i| {
|
||||
const sample = source[i];
|
||||
sum_ring_buffer.append(sample);
|
||||
pub fn applySlice(self: Transform, index: usize, source: []f64, destination: []f64) void {
|
||||
assert(source.len == destination.len);
|
||||
assert(source.len <= SampleList.Block.capacity);
|
||||
|
||||
destination[i] = sum_ring_buffer.sum / @as(f64, @floatFromInt(sum_ring_buffer.len));
|
||||
}
|
||||
}
|
||||
const gain_changes = self.gain_changes.constSlice();
|
||||
|
||||
},
|
||||
.multiply => |scalar| {
|
||||
for (0..source.len) |i| {
|
||||
destination[i] = scalar * source[i];
|
||||
}
|
||||
},
|
||||
.addition => |offset| {
|
||||
for (0..source.len) |i| {
|
||||
destination[i] = offset + source[i];
|
||||
}
|
||||
const gain_segment = Transform.findGainSegment(gain_changes, index);
|
||||
if (gain_segment.to > @as(f64, @floatFromInt(index+source.len))) {
|
||||
for (0..source.len) |i| {
|
||||
destination[i] = source[i] * (100 / gain_segment.gain);
|
||||
}
|
||||
|
||||
} else {
|
||||
const gain_per_sample = Transform.createGainLookup(gain_changes, index, source.len);
|
||||
|
||||
for (0..source.len) |i| {
|
||||
destination[i] = source[i] * (100 / gain_per_sample[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eqlSlice(slice1: []const Transform, slice2: []const Transform) bool {
|
||||
if (slice1.len != slice2.len) {
|
||||
return false;
|
||||
pub fn applyBlock(self: Transform, index: usize, source: *SampleList.Block, destination: *SampleList.Block) void {
|
||||
destination.clear();
|
||||
destination.len = source.len;
|
||||
|
||||
self.applySlice(index, source.samplesSlice(), destination.samplesSlice());
|
||||
destination.recomputeMinMax();
|
||||
}
|
||||
|
||||
pub fn applySampleList(self: Transform, from_block_index: SampleList.Block.Id, block_count: usize, source: *SampleList, destination: *SampleList) void {
|
||||
for (0..block_count) |block_offset| {
|
||||
const block_id = from_block_index + block_offset;
|
||||
|
||||
const source_block = source.getBlock(block_id).?;
|
||||
const destination_block = destination.getBlock(block_id).?;
|
||||
|
||||
self.applyBlock(block_id * SampleList.Block.capacity, source_block, destination_block);
|
||||
}
|
||||
for (0.., slice1) |i, transform1| {
|
||||
if (!std.meta.eql(transform1, slice2[i])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const GainSegment = struct {
|
||||
index: usize,
|
||||
from: f64,
|
||||
to: f64,
|
||||
gain: f64
|
||||
};
|
||||
|
||||
pub fn findGainSegment(changes: []const GainChange, index: usize) GainSegment {
|
||||
// TODO:
|
||||
// Assumes that `changes` is sorted
|
||||
assert(changes.len > 0);
|
||||
assert(changes[0].sample == 0);
|
||||
|
||||
const index_f64: f64 = @floatFromInt(index);
|
||||
|
||||
for (0..(changes.len-1)) |i| {
|
||||
if (changes[i].sample <= index_f64 and index_f64 <= changes[i+1].sample) {
|
||||
return GainSegment{
|
||||
.index = i,
|
||||
.from = changes[i].sample,
|
||||
.to = changes[i+1].sample,
|
||||
.gain = changes[i].gain
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return GainSegment{
|
||||
.index = changes.len-1,
|
||||
.from = changes[changes.len-1].sample,
|
||||
.to = std.math.inf(f64),
|
||||
.gain = changes[changes.len-1].gain
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createGainLookup(changes: []const GainChange, index: usize, size: usize) [SampleList.Block.capacity]f64 {
|
||||
assert(size <= SampleList.Block.capacity);
|
||||
|
||||
var result: [SampleList.Block.capacity]f64 = undefined;
|
||||
var last_result_index: usize = 0;
|
||||
|
||||
const from_index = findGainSegment(changes, index).index;
|
||||
|
||||
for (from_index..changes.len-1) |i| {
|
||||
const next_segment_index = @min(@as(usize, @intFromFloat(changes[i+1].sample)) - index, result.len);
|
||||
@memset(
|
||||
result[last_result_index..next_segment_index],
|
||||
changes[i].gain
|
||||
);
|
||||
|
||||
last_result_index = next_segment_index;
|
||||
if (next_segment_index == result.len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_result_index != result.len) {
|
||||
@memset(
|
||||
result[last_result_index..],
|
||||
changes[changes.len-1].gain
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn eql(self: Transform, other: Transform) bool {
|
||||
_ = self;
|
||||
_ = other;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -461,6 +493,10 @@ pub const SampleList = struct {
|
||||
block.clear();
|
||||
}
|
||||
|
||||
try self.ensureTotalBlocks(total_block_count);
|
||||
}
|
||||
|
||||
pub fn ensureTotalBlocks(self: *SampleList, total_block_count: usize) !void {
|
||||
while (self.blocks.items.len < total_block_count) {
|
||||
try self.appendEmptyBlock();
|
||||
}
|
||||
@ -541,60 +577,8 @@ pub const SampleList = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn applySlidingWindow(self: *SampleList, source: *SampleList, from_block_index: Block.Id, block_count: Block.Len, window_width: usize) !void {
|
||||
try self.applyTransformations(source, from_block_index, block_count, &[_]Transform{ .{ .sliding_window = window_width } });
|
||||
}
|
||||
|
||||
pub fn applyTransformations(self: *SampleList, source: *SampleList, from_block_index: Block.Id, block_count: Block.Len, transforms: []const Transform) !void {
|
||||
var arena = std.heap.ArenaAllocator.init(self.arena.child_allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
var states = try allocator.alloc(Transform.State, transforms.len);
|
||||
for (0..transforms.len) |i| {
|
||||
states[i] = try Transform.State.init(allocator, transforms[i]);
|
||||
}
|
||||
|
||||
for (0..transforms.len) |i| {
|
||||
states[i].prepare(transforms[i], source, from_block_index);
|
||||
}
|
||||
|
||||
for (0..block_count) |block_offset| {
|
||||
const block_id = from_block_index + block_offset;
|
||||
|
||||
const source_block = source.getBlock(block_id).?;
|
||||
const self_block = self.getBlock(block_id).?;
|
||||
self_block.clear();
|
||||
|
||||
var temp_buffer1: SampleList.Block.Buffer = undefined;
|
||||
var temp_buffer2: SampleList.Block.Buffer = undefined;
|
||||
|
||||
var front_buffer: []f64 = temp_buffer1[0..source_block.len];
|
||||
var back_buffer: []f64 = temp_buffer2[0..source_block.len];
|
||||
|
||||
for (0..transforms.len) |i| {
|
||||
var source_samples: []f64 = undefined;
|
||||
if (i == 0) {
|
||||
source_samples = source_block.samplesSlice();
|
||||
} else {
|
||||
source_samples = back_buffer;
|
||||
}
|
||||
|
||||
var destination_samples: []f64 = undefined;
|
||||
if (i == transforms.len - 1) {
|
||||
destination_samples = self_block.buffer[0..];
|
||||
} else {
|
||||
destination_samples = front_buffer;
|
||||
}
|
||||
|
||||
transforms[i].apply(&states[i], destination_samples, source_samples);
|
||||
|
||||
std.mem.swap([]f64, &front_buffer, &back_buffer);
|
||||
}
|
||||
|
||||
self_block.len = source_block.len;
|
||||
self_block.recomputeMinMax();
|
||||
}
|
||||
pub fn apply(self: *SampleList, source: *SampleList, from_block_index: Block.Id, block_count: usize, transform: Transform) void {
|
||||
transform.applySampleList(from_block_index, block_count, source, self);
|
||||
}
|
||||
|
||||
test {
|
||||
@ -750,99 +734,6 @@ pub const File = struct {
|
||||
};
|
||||
|
||||
pub const View = struct {
|
||||
pub const max_markers = 32;
|
||||
pub const max_transforms = 16;
|
||||
pub const BoundedTransformsArray = std.BoundedArray(Transform, max_transforms);
|
||||
|
||||
pub const MarkedRange = struct {
|
||||
// Persistent
|
||||
axis: UI.Axis,
|
||||
range: RangeF64,
|
||||
|
||||
// Runtime
|
||||
min: ?f64 = null,
|
||||
max: ?f64 = null,
|
||||
average: ?f64 = null,
|
||||
standard_deviation: ?f64 = null,
|
||||
|
||||
pub fn clear(self: *MarkedRange) void {
|
||||
self.min = null;
|
||||
self.max = null;
|
||||
self.average = null;
|
||||
self.standard_deviation = null;
|
||||
}
|
||||
|
||||
pub fn refresh(self: *MarkedRange, sample_list: *SampleList) void {
|
||||
self.clear();
|
||||
|
||||
if (self.axis == .X) {
|
||||
var sample_count: f64 = 0;
|
||||
var sum: f64 = 0;
|
||||
var min = std.math.inf(f64);
|
||||
var max = -std.math.inf(f64);
|
||||
|
||||
const from_sample: usize = @intFromFloat(@floor(std.math.clamp(self.range.lower, 0, @as(f64, @floatFromInt(sample_list.getLength())))));
|
||||
const to_sample: usize = @intFromFloat(@ceil(std.math.clamp(self.range.upper, 0, @as(f64, @floatFromInt(sample_list.getLength())))));
|
||||
|
||||
{
|
||||
var iter = sample_list.iterator(from_sample, to_sample);
|
||||
while (iter.next()) |segment| {
|
||||
sample_count += @floatFromInt(segment.len);
|
||||
|
||||
for (segment) |sample| {
|
||||
min = @min(min, sample);
|
||||
max = @max(max, sample);
|
||||
sum += sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_count > 0) {
|
||||
const average = sum / sample_count;
|
||||
|
||||
self.min = min;
|
||||
self.max = max;
|
||||
self.average = average;
|
||||
|
||||
if (sample_count > 1) {
|
||||
var standard_deviation: f64 = 0;
|
||||
var iter = sample_list.iterator(from_sample, to_sample);
|
||||
while (iter.next()) |segment| {
|
||||
for (segment) |sample| {
|
||||
standard_deviation += (sample - average) * (sample - average);
|
||||
}
|
||||
}
|
||||
standard_deviation /= (sample_count - 1);
|
||||
standard_deviation = @sqrt(standard_deviation);
|
||||
|
||||
self.standard_deviation = standard_deviation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const MarkedRangeIterator = struct {
|
||||
view: *View,
|
||||
axis: UI.Axis,
|
||||
index: usize = 0,
|
||||
next_index: usize = 0,
|
||||
|
||||
pub fn next(self: *MarkedRangeIterator) ?RangeF64 {
|
||||
const marked_ranges = self.view.marked_ranges.constSlice();
|
||||
while (self.next_index < marked_ranges.len) {
|
||||
self.index = self.next_index;
|
||||
const marked_range = marked_ranges[self.index];
|
||||
self.next_index += 1;
|
||||
|
||||
if (marked_range.axis == self.axis) {
|
||||
return marked_range.range;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Reference = union(enum) {
|
||||
file: Id,
|
||||
channel: Id
|
||||
@ -854,9 +745,6 @@ pub const View = struct {
|
||||
// TODO: Implement different styles of following: Look ahead, sliding, sliding window
|
||||
follow: bool = false,
|
||||
graph_opts: Graph.ViewOptions = .{},
|
||||
marked_ranges: std.BoundedArray(MarkedRange, 32) = .{},
|
||||
markers: std.BoundedArray(f64, max_markers) = .{},
|
||||
transforms: BoundedTransformsArray = .{},
|
||||
name: std.BoundedArray(u8, 128) = .{},
|
||||
|
||||
// Runtime
|
||||
@ -865,8 +753,9 @@ pub const View = struct {
|
||||
available_y_range: RangeF64 = RangeF64.init(0, 0),
|
||||
unit: ?NIDaq.Unit = .Voltage,
|
||||
|
||||
transformed_samples: ?Id = null,
|
||||
computed_transforms: BoundedTransformsArray = .{},
|
||||
transformed_samples: Id,
|
||||
computed_transform: Transform = .{},
|
||||
invalidated_transform_ranges: std.BoundedArray(RangeF64, 16) = .{},
|
||||
|
||||
pub fn clear(self: *View) void {
|
||||
self.graph_cache.clear();
|
||||
@ -887,13 +776,6 @@ pub const View = struct {
|
||||
.Y => self.available_y_range
|
||||
};
|
||||
}
|
||||
|
||||
pub fn iterMarkedRanges(self: *View, axis: UI.Axis) MarkedRangeIterator {
|
||||
return MarkedRangeIterator{
|
||||
.view = self,
|
||||
.axis = axis
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Project = struct {
|
||||
@ -913,6 +795,7 @@ pub const Project = struct {
|
||||
export_location: ?[]u8 = null,
|
||||
|
||||
solutions: std.ArrayListUnmanaged(Solution) = .{},
|
||||
gain_changes: Transform.GainChanges = .{},
|
||||
|
||||
sample_lists: GenerationalArray(SampleList) = .{},
|
||||
channels: GenerationalArray(Channel) = .{},
|
||||
@ -951,7 +834,7 @@ pub const Project = struct {
|
||||
return result_range;
|
||||
}
|
||||
|
||||
pub fn getViewSampleListId(self: *Project, view_id: Id) Id {
|
||||
pub fn getViewReferenceSampleListId(self: *Project, view_id: Id) Id {
|
||||
const view = self.views.get(view_id).?;
|
||||
|
||||
switch (view.reference) {
|
||||
@ -976,10 +859,9 @@ pub const Project = struct {
|
||||
@floatFromInt(sample_list.getLength()),
|
||||
@floatFromInt(sample_list.getLength() + samples.len)
|
||||
);
|
||||
_ = affected_range;
|
||||
|
||||
try sample_list.append(samples);
|
||||
|
||||
self.refreshMarkedRanges(sample_list_id, affected_range);
|
||||
}
|
||||
|
||||
pub fn clearSamples(self: *Project, allocator: Allocator, sample_list_id: Id) !void {
|
||||
@ -989,10 +871,10 @@ pub const Project = struct {
|
||||
0,
|
||||
@floatFromInt(sample_list.getLength())
|
||||
);
|
||||
_ = affected_range;
|
||||
|
||||
sample_list.clear(allocator);
|
||||
|
||||
self.refreshMarkedRanges(sample_list_id, affected_range);
|
||||
}
|
||||
|
||||
pub fn addSampleList(self: *Project, allocator: Allocator) !Id {
|
||||
@ -1005,32 +887,6 @@ pub const Project = struct {
|
||||
self.sample_lists.remove(sample_list_id);
|
||||
}
|
||||
|
||||
fn refreshMarkedRanges(self: *Project, sample_list_id: Id, affected_range: RangeF64) void {
|
||||
const sample_list = self.sample_lists.get(sample_list_id).?;
|
||||
_ = sample_list;
|
||||
_ = affected_range;
|
||||
|
||||
// const total_samples = sample_list.samples.items;
|
||||
|
||||
// var view_iter = self.views.idIterator();
|
||||
// while (view_iter.next()) |view_id| {
|
||||
// const view_sample_list_id = self.getViewSampleList(view_id);
|
||||
// if (!view_sample_list_id.eql(sample_list_id)) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const view = self.views.get(view_id).?;
|
||||
// const marked_ranges: []View.MarkedRange = view.marked_ranges.slice();
|
||||
// for (marked_ranges) |*marked_range| {
|
||||
// if (marked_range.axis != .X) continue;
|
||||
|
||||
// if (affected_range.intersectPositive(marked_range.range).isPositive()) {
|
||||
// marked_range.refresh(total_samples);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
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;
|
||||
@ -1045,27 +901,6 @@ pub const Project = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn appendMarkedRange(self: *Project, view_id: Id, axis: UI.Axis, range: RangeF64) ?*View.MarkedRange {
|
||||
const view = self.views.get(view_id) orelse return null;
|
||||
|
||||
if (view.marked_ranges.unusedCapacitySlice().len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const marked_range = view.marked_ranges.addOneAssumeCapacity();
|
||||
marked_range.* = View.MarkedRange{
|
||||
.axis = axis,
|
||||
.range = range
|
||||
};
|
||||
|
||||
const sample_list_id = self.getViewSampleListId(view_id);
|
||||
if (self.sample_lists.get(sample_list_id)) |sample_list| {
|
||||
marked_range.refresh(sample_list);
|
||||
}
|
||||
|
||||
return marked_range;
|
||||
}
|
||||
|
||||
pub fn getSampleTimestamp(self: *Project) ?u64 {
|
||||
var channel_iter = self.channels.iterator();
|
||||
while (channel_iter.next()) |channel| {
|
||||
@ -1108,9 +943,7 @@ pub const Project = struct {
|
||||
pub fn removeView(self: *Project, allocator: std.mem.Allocator, view_id: Id) void {
|
||||
const view = self.views.get(view_id) orelse return;
|
||||
|
||||
if (view.transformed_samples) |sample_list_id| {
|
||||
self.removeSampleList(sample_list_id);
|
||||
}
|
||||
self.removeSampleList(view.transformed_samples);
|
||||
|
||||
switch (view.reference) {
|
||||
.file => |file_id| {
|
||||
@ -1125,6 +958,13 @@ pub const Project = struct {
|
||||
view.* = undefined;
|
||||
}
|
||||
|
||||
pub fn init(self: *Project, allocator: std.mem.Allocator) !void {
|
||||
self.* = Project{};
|
||||
_ = allocator;
|
||||
|
||||
try self.gain_changes.append(.{ .gain = 100, .sample = 0 });
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Project, allocator: Allocator) void {
|
||||
var file_iter = self.files.iterator();
|
||||
while (file_iter.next()) |file| {
|
||||
@ -1146,37 +986,50 @@ pub const Project = struct {
|
||||
}
|
||||
self.sample_lists.clear();
|
||||
|
||||
|
||||
if (self.solutions.capacity > 0) {
|
||||
for (self.solutions.items) |solution| {
|
||||
allocator.free(solution.description);
|
||||
}
|
||||
self.solutions.deinit(allocator);
|
||||
for (self.solutions.items) |solution| {
|
||||
allocator.free(solution.description);
|
||||
}
|
||||
self.solutions.deinit(allocator);
|
||||
|
||||
if (self.pipete_solution.capacity > 0) {
|
||||
self.pipete_solution.deinit(allocator);
|
||||
}
|
||||
|
||||
if (self.notes.capacity > 0) {
|
||||
self.notes.deinit(allocator);
|
||||
}
|
||||
self.pipete_solution.deinit(allocator);
|
||||
self.notes.deinit(allocator);
|
||||
|
||||
if (self.export_location) |export_location| {
|
||||
allocator.free(export_location);
|
||||
}
|
||||
|
||||
if (self.statistic_points.items.len > 0) {
|
||||
self.statistic_points.deinit(allocator);
|
||||
self.statistic_points.deinit(allocator);
|
||||
self.experiment_name.deinit(allocator);
|
||||
}
|
||||
|
||||
pub fn getViewTransform(self: *Project, view_id: Id) Transform {
|
||||
_ = view_id;
|
||||
return Transform{
|
||||
.gain_changes = self.gain_changes
|
||||
};
|
||||
}
|
||||
|
||||
pub fn removeGainChange(self: *Project, index: usize) void {
|
||||
const gain_change = self.gain_changes.orderedRemove(index);
|
||||
|
||||
const invalidate_lower_bound = gain_change.sample;
|
||||
var invalidate_upper_bound = std.math.inf(f64);
|
||||
if (index < self.gain_changes.len) {
|
||||
invalidate_upper_bound = self.gain_changes.get(index).sample;
|
||||
}
|
||||
|
||||
self.experiment_name.deinit(allocator);
|
||||
const invalidate_range = RangeF64.init(invalidate_lower_bound, invalidate_upper_bound);
|
||||
|
||||
var view_iter = self.views.iterator();
|
||||
while (view_iter.next()) |view| {
|
||||
view.invalidated_transform_ranges.append(invalidate_range) catch continue;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- Serialization ------------------ //
|
||||
|
||||
pub fn initFromFile(self: *Project, allocator: Allocator, f: std.fs.File) !void {
|
||||
self.* = .{};
|
||||
try Project.init(self, allocator);
|
||||
errdefer self.deinit(allocator);
|
||||
|
||||
const reader = f.reader();
|
||||
@ -1269,9 +1122,13 @@ pub const Project = struct {
|
||||
return error.InvalidReferenceTag;
|
||||
}
|
||||
|
||||
const sample_list_id = try self.addSampleList(allocator);
|
||||
errdefer self.removeSampleList(sample_list_id);
|
||||
|
||||
const view = self.views.get(id).?;
|
||||
view.* = View{
|
||||
.reference = reference,
|
||||
.transformed_samples = sample_list_id
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1439,7 +1296,7 @@ const WorkJob = struct {
|
||||
|
||||
view_id: Id,
|
||||
stage: Stage = .init,
|
||||
transforms: View.BoundedTransformsArray = .{},
|
||||
transform: Transform = .{},
|
||||
|
||||
mutex: std.Thread.Mutex = .{},
|
||||
running_thread_jobs: std.ArrayListUnmanaged(SampleList.Block.Id) = .{},
|
||||
@ -1488,33 +1345,20 @@ const WorkJob = struct {
|
||||
const project = &app.project;
|
||||
const view = project.views.get(self.view_id) orelse return true;
|
||||
|
||||
const sample_list_id = project.getViewSampleListId(self.view_id);
|
||||
const sample_list_id = project.getViewReferenceSampleListId(self.view_id);
|
||||
const sample_list = project.sample_lists.get(sample_list_id).?;
|
||||
|
||||
const transforms = self.transforms.constSlice();
|
||||
if (!Transform.eqlSlice(view.transforms.constSlice(), transforms)) {
|
||||
if (!Transform.eql(project.getViewTransform(self.view_id), self.transform)) {
|
||||
// Transforms changed, job needs to be cancelled.
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (self.stage) {
|
||||
.init => {
|
||||
if (transforms.len == 0) {
|
||||
if (view.transformed_samples) |transformed_samples_id| {
|
||||
project.removeSampleList(transformed_samples_id);
|
||||
}
|
||||
view.transformed_samples = null;
|
||||
const transformed_samples = project.sample_lists.get(view.transformed_samples).?;
|
||||
try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (view.transformed_samples == null) {
|
||||
view.transformed_samples = try project.addSampleList(app.allocator);
|
||||
}
|
||||
const transformed_samples = project.sample_lists.get(view.transformed_samples.?).?;
|
||||
try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len);
|
||||
|
||||
self.stage = .launch_threads;
|
||||
}
|
||||
self.stage = .launch_threads;
|
||||
},
|
||||
.launch_threads => {
|
||||
const max_block_to_process = 256;
|
||||
@ -1548,15 +1392,12 @@ const WorkJob = struct {
|
||||
// }
|
||||
|
||||
const view = project.views.get(self.view_id) orelse return;
|
||||
const transformed_samples_id = view.transformed_samples orelse return;
|
||||
const transformed_samples = project.sample_lists.get(transformed_samples_id) orelse return;
|
||||
const transformed_samples = project.sample_lists.get(view.transformed_samples) orelse return;
|
||||
|
||||
const sample_list_id = project.getViewSampleListId(self.view_id);
|
||||
const sample_list_id = project.getViewReferenceSampleListId(self.view_id);
|
||||
const sample_list = project.sample_lists.get(sample_list_id) orelse return;
|
||||
|
||||
transformed_samples.applyTransformations(sample_list, from_block_id, block_count, self.transforms.constSlice()) catch |e| {
|
||||
log.err("Failed to compute sliding window: {}", .{e});
|
||||
};
|
||||
transformed_samples.apply(sample_list, from_block_id, block_count, self.transform);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1572,7 +1413,7 @@ screen: enum {
|
||||
} = .main,
|
||||
main_screen: MainScreen,
|
||||
channel_from_device: ChannelFromDeviceScreen,
|
||||
project: Project = .{},
|
||||
project: Project,
|
||||
|
||||
collection_mutex: std.Thread.Mutex = .{},
|
||||
collection_samples_mutex: std.Thread.Mutex = .{},
|
||||
@ -1594,8 +1435,10 @@ pub fn init(self: *App, allocator: Allocator) !void {
|
||||
.main_screen = undefined,
|
||||
.collection_thread = undefined,
|
||||
.channel_from_device = undefined,
|
||||
.work_thread_pool = undefined
|
||||
.work_thread_pool = undefined,
|
||||
.project = undefined
|
||||
};
|
||||
try Project.init(&self.project, allocator);
|
||||
try self.initUI();
|
||||
try self.work_thread_pool.init(.{
|
||||
.allocator = allocator
|
||||
@ -1689,9 +1532,8 @@ pub fn loadProject(self: *App, file: std.fs.File) !void {
|
||||
var loaded = try self.allocator.create(Project);
|
||||
defer self.allocator.destroy(loaded);
|
||||
|
||||
loaded.* = .{};
|
||||
errdefer loaded.deinit(self.allocator);
|
||||
try loaded.initFromFile(self.allocator, file);
|
||||
errdefer loaded.deinit(self.allocator);
|
||||
|
||||
self.deinitUI();
|
||||
self.deinitProject();
|
||||
@ -1889,6 +1731,40 @@ pub fn tick(self: *App) !void {
|
||||
}
|
||||
|
||||
try self.showUI();
|
||||
|
||||
{
|
||||
const block_size = SampleList.Block.capacity;
|
||||
|
||||
var view_iter = self.project.views.idIterator();
|
||||
while (view_iter.next()) |view_id| {
|
||||
const view = self.getView(view_id).?;
|
||||
|
||||
const referenced_samples_id = self.project.getViewReferenceSampleListId(view_id);
|
||||
const referenced_samples = self.project.sample_lists.get(referenced_samples_id).?;
|
||||
const computed_samples_id = view.transformed_samples;
|
||||
const computed_samples = self.project.sample_lists.get(computed_samples_id).?;
|
||||
|
||||
const transform = self.project.getViewTransform(view_id);
|
||||
const computed_len = computed_samples.getLength();
|
||||
const reference_len = referenced_samples.getLength();
|
||||
|
||||
if (reference_len > computed_len) {
|
||||
view.invalidated_transform_ranges.append(RangeF64.init(@floatFromInt(computed_len), @floatFromInt(reference_len))) catch {};
|
||||
}
|
||||
|
||||
for (view.invalidated_transform_ranges.constSlice()) |range| {
|
||||
const from_block: usize = @intFromFloat(@divFloor(range.lower, block_size));
|
||||
const to_block: usize = @intFromFloat(@divFloor(@min(range.upper, @as(f64, @floatFromInt(reference_len)) - 1), block_size));
|
||||
const block_count = to_block - from_block + 1;
|
||||
|
||||
try computed_samples.ensureTotalBlocks(from_block + block_count);
|
||||
|
||||
transform.applySampleList(@intCast(from_block), block_count, referenced_samples, computed_samples);
|
||||
view.graph_cache.invalidateRange(range);
|
||||
}
|
||||
view.invalidated_transform_ranges.len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rl.clearBackground(srcery.black);
|
||||
@ -1957,18 +1833,20 @@ pub fn tick(self: *App) !void {
|
||||
var view_iter = self.project.views.idIterator();
|
||||
while (view_iter.next()) |view_id| {
|
||||
const view = self.project.views.get(view_id).?;
|
||||
_ = view;
|
||||
|
||||
if (Transform.eqlSlice(view.computed_transforms.constSlice(), view.transforms.constSlice())) {
|
||||
continue;
|
||||
}
|
||||
// TODO:
|
||||
// if (Transform.eqlSlice(view.computed_transforms.constSlice(), view.transforms.constSlice())) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
_ = try self.work_jobs.insert(WorkJob{
|
||||
.transforms = view.transforms,
|
||||
.view_id = view_id
|
||||
});
|
||||
// _ = try self.work_jobs.insert(WorkJob{
|
||||
// .transforms = view.transforms,
|
||||
// .view_id = view_id
|
||||
// });
|
||||
|
||||
view.computed_transforms.len = 0;
|
||||
view.computed_transforms.appendSliceAssumeCapacity(view.transforms.constSlice());
|
||||
// view.computed_transforms.len = 0;
|
||||
// view.computed_transforms.appendSliceAssumeCapacity(view.transforms.constSlice());
|
||||
}
|
||||
}
|
||||
|
||||
@ -2087,7 +1965,7 @@ fn startCollection(self: *App) !void {
|
||||
self.collection_condition.signal();
|
||||
}
|
||||
|
||||
fn stopCollection(self: *App) void {
|
||||
pub fn stopCollection(self: *App) void {
|
||||
if (!self.isCollectionInProgress()) {
|
||||
return;
|
||||
}
|
||||
@ -2140,7 +2018,7 @@ fn startOutput(self: *App, channel_id: Id) !void {
|
||||
channel.output_task = task;
|
||||
}
|
||||
|
||||
fn stopOutput(self: *App, channel_id: Id) void {
|
||||
pub fn stopOutput(self: *App, channel_id: Id) void {
|
||||
const channel = self.getChannel(channel_id) orelse return;
|
||||
|
||||
if (channel.output_task == null) {
|
||||
@ -2167,6 +2045,13 @@ pub fn isOutputingInProgress(self: *App) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn stopAllOutput(self: *App) void {
|
||||
var channel_iter = self.project.channels.idIterator();
|
||||
while (channel_iter.next()) |channel_id| {
|
||||
self.stopOutput(channel_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn isNiDaqInUse(self: *App) bool {
|
||||
return self.isCollectionInProgress() or self.isOutputingInProgress();
|
||||
}
|
||||
@ -2371,9 +2256,13 @@ pub fn addView(self: *App, reference: View.Reference) !Id {
|
||||
const id = try self.project.views.insertUndefined();
|
||||
errdefer self.project.views.remove(id);
|
||||
|
||||
const sample_list_id = try self.project.addSampleList(self.allocator);
|
||||
errdefer self.project.removeSampleList(sample_list_id);
|
||||
|
||||
const view = self.project.views.get(id).?;
|
||||
view.* = View{
|
||||
.reference = reference
|
||||
.reference = reference,
|
||||
.transformed_samples = sample_list_id
|
||||
};
|
||||
|
||||
self.loadView(id) catch |e| {
|
||||
@ -2385,7 +2274,7 @@ pub fn addView(self: *App, reference: View.Reference) !Id {
|
||||
|
||||
fn refreshViewAvailableXYRanges(self: *App, id: Id) void {
|
||||
const view = self.getView(id) orelse return;
|
||||
const sample_list_id = self.project.getViewSampleListId(id);
|
||||
const sample_list_id = self.project.getViewReferenceSampleListId(id);
|
||||
const sample_list = self.project.sample_lists.get(sample_list_id).?;
|
||||
|
||||
view.available_x_range = RangeF64.init(0, @floatFromInt(sample_list.getLength()));
|
||||
|
@ -153,6 +153,7 @@ const MarkedRangeIterator = struct {
|
||||
}
|
||||
};
|
||||
|
||||
app: *App,
|
||||
project: *App.Project,
|
||||
|
||||
// TODO: Redo
|
||||
@ -160,21 +161,13 @@ undo_stack: CommandFrameArray = .{},
|
||||
last_applied_command: usize = 0,
|
||||
zoom_start: ?ViewAxisPosition = null,
|
||||
cursor: ?ViewAxisPosition = null,
|
||||
view_settings: ?Id = null, // View id
|
||||
view_protocol_modal: ?Id = null, // View id
|
||||
selected_tool: enum { move, select, marker } = .move,
|
||||
show_marked_range: ?struct {
|
||||
view_id: Id,
|
||||
index: usize,
|
||||
} = null,
|
||||
show_marker: ?struct {
|
||||
view_id: Id,
|
||||
index: usize
|
||||
} = null,
|
||||
// selected_tool: enum { move, select, marker } = .move,
|
||||
|
||||
pub fn init(project: *App.Project) System {
|
||||
pub fn init(app: *App) System {
|
||||
return System{
|
||||
.project = project
|
||||
.app = app,
|
||||
.project = &app.project
|
||||
};
|
||||
}
|
||||
|
||||
@ -293,14 +286,15 @@ pub fn applyCommands(self: *System) void {
|
||||
|
||||
pub fn toggleViewSettings(self: *System, view_id: Id) void {
|
||||
if (self.isViewSettingsOpen(view_id)) {
|
||||
self.view_settings = null;
|
||||
self.app.main_screen.side_panel = .project;
|
||||
} else {
|
||||
self.view_settings = view_id;
|
||||
self.app.main_screen.side_panel = .{ .view_settings = view_id };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isViewSettingsOpen(self: *System, view_id: Id) bool {
|
||||
return self.view_settings != null and self.view_settings.?.eql(view_id);
|
||||
const side_panel = self.app.main_screen.side_panel;
|
||||
return side_panel == .view_settings and side_panel.view_settings.eql(view_id);
|
||||
}
|
||||
|
||||
pub fn setCursor(self: *System, view_id: Id, axis: UI.Axis, position: ?f64) void {
|
||||
@ -320,29 +314,36 @@ pub fn getCursorHoldStart(self: *System, view_id: Id, axis: UI.Axis) ?f64 {
|
||||
}
|
||||
|
||||
pub fn toggleShownMarkedRange(self: *System, view_id: Id, index: usize) void {
|
||||
if (self.show_marked_range) |show_marked_range| {
|
||||
const side_panel = &self.app.main_screen.side_panel;
|
||||
|
||||
if (side_panel.* == .marked_range) {
|
||||
const show_marked_range = side_panel.marked_range;
|
||||
if (show_marked_range.view_id.eql(view_id) and show_marked_range.index == index) {
|
||||
self.show_marked_range = null;
|
||||
side_panel.* = .project;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.show_marked_range = .{
|
||||
.view_id = view_id,
|
||||
.index = index,
|
||||
side_panel.* = .{
|
||||
.marked_range = .{ .index = index, .view_id = view_id }
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toggleShownMarker(self: *System, view_id: Id, index: usize) void {
|
||||
if (self.show_marker) |show_marker| {
|
||||
const side_panel = &self.app.main_screen.side_panel;
|
||||
|
||||
if (side_panel.* == .marker) {
|
||||
const show_marker = side_panel.marker;
|
||||
if (show_marker.view_id.eql(view_id) and show_marker.index == index) {
|
||||
self.show_marker = null;
|
||||
side_panel.* = .project;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.show_marker = .{
|
||||
.view_id = view_id,
|
||||
.index = index,
|
||||
side_panel.* = .{
|
||||
.marker = .{
|
||||
.view_id = view_id,
|
||||
.index = index,
|
||||
}
|
||||
};
|
||||
}
|
@ -73,7 +73,7 @@ fn showGraph(ctx: Context, graph_box: *UI.Box, view_id: Id) void {
|
||||
sample_value_under_mouse = mouse_y_range.remapTo(view_opts.y_range, signal.relative_mouse.y);
|
||||
}
|
||||
|
||||
if (ctx.view_controls.selected_tool == .move) {
|
||||
// if (ctx.view_controls.selected_tool == .move) {
|
||||
if (signal.dragged()) {
|
||||
const x_offset = mouse_x_range.remapTo(RangeF64.init(0, view_opts.x_range.size()), signal.drag.x);
|
||||
const y_offset = mouse_y_range.remapTo(RangeF64.init(0, view_opts.y_range.size()), signal.drag.y);
|
||||
@ -107,21 +107,16 @@ fn showGraph(ctx: Context, graph_box: *UI.Box, view_id: Id) void {
|
||||
if (signal.flags.contains(.left_released)) {
|
||||
ctx.view_controls.pushBreakpoint();
|
||||
}
|
||||
} else if (ctx.view_controls.selected_tool == .select) {
|
||||
// TODO:
|
||||
// } else if (ctx.view_controls.selected_tool == .select) {
|
||||
// // TODO:
|
||||
|
||||
} else if (ctx.view_controls.selected_tool == .marker) {
|
||||
// TODO:
|
||||
}
|
||||
// } else if (ctx.view_controls.selected_tool == .marker) {
|
||||
// // TODO:
|
||||
// }
|
||||
|
||||
|
||||
{ // Render graph
|
||||
var sample_list_id = app.project.getViewSampleListId(view_id);
|
||||
if (view.transformed_samples) |transformed_samples_id| {
|
||||
sample_list_id = transformed_samples_id;
|
||||
}
|
||||
|
||||
const sample_list = app.project.sample_lists.get(sample_list_id).?;
|
||||
const sample_list = app.project.sample_lists.get(view.transformed_samples).?;
|
||||
Graph.drawCached(&view.graph_cache, graph_box.persistent.size, view_opts.*, sample_list);
|
||||
if (view.graph_cache.texture) |texture| {
|
||||
graph_box.texture = texture.texture;
|
||||
|
@ -329,54 +329,87 @@ pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: U
|
||||
_ = showMarkerRect(ui, ruler, RangeF64.init(hold_start.?, cursor.?), marker_color.alpha(0.5), null);
|
||||
}
|
||||
|
||||
{
|
||||
var selected_range_iter = view.iterMarkedRanges(axis);
|
||||
while (selected_range_iter.next()) |selected_range| {
|
||||
var color = srcery.blue;
|
||||
const index = selected_range_iter.index;
|
||||
// {
|
||||
// var selected_range_iter = view.iterMarkedRanges(axis);
|
||||
// while (selected_range_iter.next()) |selected_range| {
|
||||
// var color = srcery.blue;
|
||||
// const index = selected_range_iter.index;
|
||||
|
||||
if (ctx.view_controls.show_marked_range) |show_marked_range| {
|
||||
if (show_marked_range.view_id.eql(view_id) and show_marked_range.index == index) {
|
||||
if (@mod(rl.getTime(), 0.5) < 0.25) {
|
||||
color = utils.shiftColorInHSV(color, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
// const side_panel = ctx.view_controls.app.main_screen.side_panel;
|
||||
// if (side_panel == .marked_range) {
|
||||
// const show_marked_range = side_panel.marked_range;
|
||||
// if (show_marked_range.view_id.eql(view_id) and show_marked_range.index == index) {
|
||||
// if (@mod(rl.getTime(), 0.5) < 0.25) {
|
||||
// color = utils.shiftColorInHSV(color, 0.8);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
showMarkerLine(ui, ruler, selected_range.lower, color);
|
||||
showMarkerLine(ui, ruler, selected_range.upper, color);
|
||||
// showMarkerLine(ui, ruler, selected_range.lower, color);
|
||||
// showMarkerLine(ui, ruler, selected_range.upper, color);
|
||||
|
||||
var hasher = UI.Key.CombineHasher.init();
|
||||
hasher.update(std.mem.asBytes("Marked ranges"));
|
||||
hasher.update(std.mem.asBytes(&view_id));
|
||||
hasher.update(std.mem.asBytes(&axis));
|
||||
hasher.update(std.mem.asBytes(&index));
|
||||
const range_box_key = UI.Key.init(hasher.final());
|
||||
// var hasher = UI.Key.CombineHasher.init();
|
||||
// hasher.update(std.mem.asBytes("Marked ranges"));
|
||||
// hasher.update(std.mem.asBytes(&view_id));
|
||||
// hasher.update(std.mem.asBytes(&axis));
|
||||
// hasher.update(std.mem.asBytes(&index));
|
||||
// const range_box_key = UI.Key.init(hasher.final());
|
||||
|
||||
var range_box = showMarkerRect(ui, ruler, selected_range, color.alpha(0.5), range_box_key);
|
||||
range_box.flags.insert(.clickable);
|
||||
range_box.flags.insert(.draw_hot);
|
||||
range_box.flags.insert(.draw_active);
|
||||
// var range_box = showMarkerRect(ui, ruler, selected_range, color.alpha(0.5), range_box_key);
|
||||
// range_box.flags.insert(.clickable);
|
||||
// range_box.flags.insert(.draw_hot);
|
||||
// range_box.flags.insert(.draw_active);
|
||||
|
||||
range_box.hot_cursor = .mouse_cursor_pointing_hand;
|
||||
if (ctx.view_controls.selected_tool == .select) {
|
||||
const signal = ui.signal(range_box);
|
||||
if (signal.clicked()) {
|
||||
ctx.view_controls.toggleShownMarkedRange(view_id, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// range_box.hot_cursor = .mouse_cursor_pointing_hand;
|
||||
// if (ctx.view_controls.selected_tool == .select) {
|
||||
// const signal = ui.signal(range_box);
|
||||
// if (signal.clicked()) {
|
||||
// ctx.view_controls.toggleShownMarkedRange(view_id, index);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (axis == .X) {
|
||||
for (0.., view.markers.constSlice()) |i, marker| {
|
||||
const color = srcery.cyan;
|
||||
// for (0.., view.markers.constSlice()) |i, marker| {
|
||||
// const color = srcery.cyan;
|
||||
|
||||
showMarkerLine(ui, ruler, marker, color);
|
||||
showMarkerLine(ui, ruler, marker, color);
|
||||
// showMarkerLine(ui, ruler, marker, color);
|
||||
// showMarkerLine(ui, ruler, marker, color);
|
||||
|
||||
// var hasher = UI.Key.CombineHasher.init();
|
||||
// hasher.update(std.mem.asBytes("Markers"));
|
||||
// hasher.update(std.mem.asBytes(&view_id));
|
||||
// hasher.update(std.mem.asBytes(&axis));
|
||||
// hasher.update(std.mem.asBytes(&i));
|
||||
|
||||
// const view_size = view.graph_opts.x_range.size();
|
||||
// const clickable_width = view_size * 0.01;
|
||||
|
||||
// const clickable = ui.createBox(.{
|
||||
// .key = UI.Key.init(hasher.final()),
|
||||
// .float_rect = ruler.getGraphDrawContext().getRect(marker - clickable_width/2, clickable_width, 0, 1),
|
||||
// .float_relative_to = ruler.graph_box,
|
||||
// .parent = ruler.graph_box,
|
||||
// .flags = &.{ .draw_hot, .draw_active, .clickable },
|
||||
// .hot_cursor = .mouse_cursor_pointing_hand,
|
||||
// });
|
||||
|
||||
// if (ui.signal(clickable).clicked()) {
|
||||
// ctx.view_controls.toggleShownMarker(view_id, i);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (0.., project.gain_changes.slice()) |i, gain_change| {
|
||||
const color = srcery.bright_orange;
|
||||
|
||||
const sample = gain_change.sample;
|
||||
|
||||
showMarkerLine(ui, ruler, sample, color);
|
||||
showMarkerLine(ui, ruler, sample, color);
|
||||
|
||||
var hasher = UI.Key.CombineHasher.init();
|
||||
hasher.update(std.mem.asBytes("Markers"));
|
||||
hasher.update(std.mem.asBytes("Gain change"));
|
||||
hasher.update(std.mem.asBytes(&view_id));
|
||||
hasher.update(std.mem.asBytes(&axis));
|
||||
hasher.update(std.mem.asBytes(&i));
|
||||
@ -386,15 +419,20 @@ pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: U
|
||||
|
||||
const clickable = ui.createBox(.{
|
||||
.key = UI.Key.init(hasher.final()),
|
||||
.float_rect = ruler.getGraphDrawContext().getRect(marker - clickable_width/2, clickable_width, 0, 1),
|
||||
.float_rect = ruler.getGraphDrawContext().getRect(sample - clickable_width/2, clickable_width, 0, 1),
|
||||
.float_relative_to = ruler.graph_box,
|
||||
.parent = ruler.graph_box,
|
||||
.flags = &.{ .draw_hot, .draw_active, .clickable },
|
||||
.hot_cursor = .mouse_cursor_pointing_hand,
|
||||
});
|
||||
|
||||
if (ui.signal(clickable).clicked()) {
|
||||
ctx.view_controls.toggleShownMarker(view_id, i);
|
||||
const signal = ui.signal(clickable);
|
||||
if (signal.hot) {
|
||||
const mouse_tooltip = ui.mouseTooltip();
|
||||
mouse_tooltip.beginChildren();
|
||||
defer mouse_tooltip.endChildren();
|
||||
|
||||
_ = ui.label("Gain: {d:.3}", .{ gain_change.gain });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -427,7 +465,7 @@ pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: U
|
||||
ctx.view_controls.setCursorHoldStart(view_id, axis, cursor);
|
||||
}
|
||||
|
||||
if (ctx.view_controls.selected_tool == .move) {
|
||||
// if (ctx.view_controls.selected_tool == .move) {
|
||||
if (signal.scrolled() and cursor != null) {
|
||||
var scale_factor: f64 = 1;
|
||||
if (signal.scroll.y > 0) {
|
||||
@ -462,27 +500,27 @@ pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: U
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (ctx.view_controls.selected_tool == .select) {
|
||||
// } else if (ctx.view_controls.selected_tool == .select) {
|
||||
|
||||
if (cursor != null) {
|
||||
if (ctx.view_controls.getCursorHoldStart(view_id, axis)) |hold_start| {
|
||||
const range = RangeF64.init(
|
||||
@min(hold_start, cursor.?),
|
||||
@max(hold_start, cursor.?),
|
||||
);
|
||||
const hold_start_mouse = view_range.remapTo(mouse_range, range.lower);
|
||||
const hold_end_mouse = view_range.remapTo(mouse_range, range.upper);
|
||||
const mouse_move_distance = @abs(hold_end_mouse - hold_start_mouse);
|
||||
if (signal.flags.contains(.left_released) and mouse_move_distance > 5) {
|
||||
_ = ctx.project.appendMarkedRange(view_id, axis, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (ctx.view_controls.selected_tool == .marker) {
|
||||
if (cursor != null and signal.flags.contains(.left_released) and axis == .X) {
|
||||
view.markers.append(cursor.?) catch {};
|
||||
}
|
||||
}
|
||||
// if (cursor != null) {
|
||||
// if (ctx.view_controls.getCursorHoldStart(view_id, axis)) |hold_start| {
|
||||
// const range = RangeF64.init(
|
||||
// @min(hold_start, cursor.?),
|
||||
// @max(hold_start, cursor.?),
|
||||
// );
|
||||
// const hold_start_mouse = view_range.remapTo(mouse_range, range.lower);
|
||||
// const hold_end_mouse = view_range.remapTo(mouse_range, range.upper);
|
||||
// const mouse_move_distance = @abs(hold_end_mouse - hold_start_mouse);
|
||||
// if (signal.flags.contains(.left_released) and mouse_move_distance > 5) {
|
||||
// _ = ctx.project.appendMarkedRange(view_id, axis, range);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else if (ctx.view_controls.selected_tool == .marker) {
|
||||
// if (cursor != null and signal.flags.contains(.left_released) and axis == .X) {
|
||||
// view.markers.append(cursor.?) catch {};
|
||||
// }
|
||||
// }
|
||||
|
||||
if (signal.flags.contains(.left_released)) {
|
||||
ctx.view_controls.setCursorHoldStart(view_id, axis, null);
|
||||
|
@ -30,7 +30,7 @@ pub const ViewOptions = struct {
|
||||
pub const RenderCache = struct {
|
||||
const Key = struct {
|
||||
options: ViewOptions,
|
||||
drawn_x_range: RangeF64
|
||||
drawn_x_range: RangeF64,
|
||||
};
|
||||
|
||||
texture: ?rl.RenderTexture2D = null,
|
||||
@ -48,6 +48,14 @@ pub const RenderCache = struct {
|
||||
self.key = null;
|
||||
}
|
||||
|
||||
pub fn invalidateRange(self: *RenderCache, x_range: RangeF64) void {
|
||||
if (self.key) |key| {
|
||||
if (key.drawn_x_range.intersectPositive(x_range).isPositive()) {
|
||||
self.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(self: RenderCache, rect: rl.Rectangle) void {
|
||||
if (self.texture) |texture| {
|
||||
const source = rl.Rectangle{
|
||||
@ -323,7 +331,7 @@ pub fn drawCached(cache: *RenderCache, render_size: Vec2, options: ViewOptions,
|
||||
|
||||
const cache_key = RenderCache.Key{
|
||||
.options = options,
|
||||
.drawn_x_range = RangeF64.init(0, @max(@as(f64, @floatFromInt(sample_list.getLength())) - 1, 0)).intersectPositive(options.x_range)
|
||||
.drawn_x_range = RangeF64.init(0, @max(@as(f64, @floatFromInt(sample_list.getLength())) - 1, 0)).intersectPositive(options.x_range),
|
||||
};
|
||||
|
||||
if (cache.key != null and std.meta.eql(cache.key.?, cache_key)) {
|
||||
|
19
src/main.zig
19
src/main.zig
@ -119,12 +119,12 @@ pub fn main() !void {
|
||||
if (app_config_dir.openFile("config.bin", .{})) |save_file| {
|
||||
defer save_file.close();
|
||||
|
||||
_ = try app.addView(.{
|
||||
.file = try app.addFile("./samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin")
|
||||
});
|
||||
// app.loadProject(save_file) catch |e| {
|
||||
// log.err("Failed to load project: {}", .{e});
|
||||
// };
|
||||
app.loadProject(save_file) catch |e| {
|
||||
log.err("Failed to load project: {}", .{e});
|
||||
if (@errorReturnTrace()) |stack_trace| {
|
||||
std.debug.dumpStackTrace(stack_trace.*);
|
||||
}
|
||||
};
|
||||
} else |e| switch (e) {
|
||||
error.FileNotFound => {},
|
||||
else => return e
|
||||
@ -178,6 +178,13 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
{
|
||||
if (app.isCollectionInProgress()) {
|
||||
app.stopCollection();
|
||||
}
|
||||
if (app.isOutputingInProgress()) {
|
||||
app.stopAllOutput();
|
||||
}
|
||||
|
||||
const save_file = try app_config_dir.createFile("config.bin", .{});
|
||||
defer save_file.close();
|
||||
try app.saveProject(save_file);
|
||||
|
@ -22,8 +22,22 @@ const Id = App.Id;
|
||||
|
||||
app: *App,
|
||||
view_controls: ViewControlsSystem,
|
||||
view_solutions: bool = false,
|
||||
view_statistics_points: bool = false,
|
||||
side_panel: union(enum) {
|
||||
project,
|
||||
solutions,
|
||||
gain_changes,
|
||||
statistics_points,
|
||||
view_settings: Id,
|
||||
// marker: struct {
|
||||
// view_id: Id,
|
||||
// index: usize
|
||||
// },
|
||||
// marked_range: struct {
|
||||
// view_id: Id,
|
||||
// index: usize,
|
||||
// }
|
||||
} = .project,
|
||||
|
||||
modal: ?union(enum){
|
||||
view_protocol: Id, // View id
|
||||
notes
|
||||
@ -42,6 +56,7 @@ notes_storage: UI.TextInputStorage,
|
||||
|
||||
// Project settings
|
||||
solution_input: UI.TextInputStorage,
|
||||
gain_input: UI.TextInputStorage,
|
||||
sample_rate_input: UI.TextInputStorage,
|
||||
experiment_name: UI.TextInputStorage,
|
||||
pipete_solution: UI.TextInputStorage,
|
||||
@ -50,7 +65,6 @@ parsed_sample_rate: ?f64 = null,
|
||||
|
||||
// View settings
|
||||
prev_view_settings: ?Id = null, // View ID
|
||||
transform_inputs: [App.View.max_transforms]UI.TextInputStorage,
|
||||
view_name_input: UI.TextInputStorage,
|
||||
channel_save_file_picker: ?Platform.FilePickerId = null,
|
||||
file_save_file_picker: ?Platform.FilePickerId = null,
|
||||
@ -58,11 +72,6 @@ file_save_file_picker: ?Platform.FilePickerId = null,
|
||||
pub fn init(app: *App) !MainScreen {
|
||||
const allocator = app.allocator;
|
||||
|
||||
var transform_inputs: [App.View.max_transforms]UI.TextInputStorage = undefined;
|
||||
for (&transform_inputs) |*input| {
|
||||
input.* = UI.TextInputStorage.init(allocator);
|
||||
}
|
||||
|
||||
var self = MainScreen{
|
||||
.app = app,
|
||||
.frequency_input = UI.TextInputStorage.init(allocator),
|
||||
@ -73,8 +82,8 @@ pub fn init(app: *App) !MainScreen {
|
||||
.experiment_name = UI.TextInputStorage.init(allocator),
|
||||
.pipete_solution = UI.TextInputStorage.init(allocator),
|
||||
.view_name_input = UI.TextInputStorage.init(allocator),
|
||||
.view_controls = ViewControlsSystem.init(&app.project),
|
||||
.transform_inputs = transform_inputs,
|
||||
.gain_input = UI.TextInputStorage.init(allocator),
|
||||
.view_controls = ViewControlsSystem.init(app),
|
||||
.preview_sample_list_id = try app.project.addSampleList(allocator)
|
||||
};
|
||||
|
||||
@ -93,9 +102,7 @@ pub fn deinit(self: *MainScreen) void {
|
||||
self.experiment_name.deinit();
|
||||
self.pipete_solution.deinit();
|
||||
self.view_name_input.deinit();
|
||||
for (self.transform_inputs) |input| {
|
||||
input.deinit();
|
||||
}
|
||||
self.gain_input.deinit();
|
||||
self.app.project.removeSampleList(self.preview_sample_list_id);
|
||||
|
||||
self.clearProtocolErrorMessage();
|
||||
@ -376,6 +383,7 @@ fn showProjectSettings(self: *MainScreen) !void {
|
||||
{
|
||||
_ = ui.label("Solution:", .{});
|
||||
|
||||
|
||||
try ui.textInput(.{
|
||||
.key = ui.keyFromString("Solution input"),
|
||||
.storage = &self.solution_input,
|
||||
@ -407,7 +415,7 @@ fn showProjectSettings(self: *MainScreen) !void {
|
||||
const btn = ui.textButton("View solutions");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.view_solutions = true;
|
||||
self.side_panel = .solutions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -416,7 +424,36 @@ fn showProjectSettings(self: *MainScreen) !void {
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(0.5)) });
|
||||
|
||||
{
|
||||
{ // Gain
|
||||
_ = ui.label("Gain:", .{});
|
||||
|
||||
const last_gain_change = project.gain_changes.buffer[project.gain_changes.len - 1];
|
||||
const current_gain = try ui.numberInput(f64, .{
|
||||
.key = ui.keyFromString("Gain input"),
|
||||
.storage = &self.gain_input,
|
||||
.initial = last_gain_change.gain,
|
||||
.width = 400
|
||||
});
|
||||
|
||||
if (!self.gain_input.editing and current_gain != null and current_gain.? != last_gain_change.gain) {
|
||||
try project.gain_changes.append(.{
|
||||
.gain = current_gain.?,
|
||||
.sample = @floatFromInt(project.getSampleTimestamp() orelse 0)
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const btn = ui.textButton("Show gain changes");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.side_panel = .gain_changes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(0.5)) });
|
||||
|
||||
{ // Statistic points
|
||||
const row = ui.createBox(.{
|
||||
.size_x = UI.Sizing.initFitChildren(),
|
||||
.size_y = UI.Sizing.initFitChildren(),
|
||||
@ -439,7 +476,7 @@ fn showProjectSettings(self: *MainScreen) !void {
|
||||
const btn = ui.textButton("View statistic points");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.view_statistics_points = true;
|
||||
self.side_panel = .statistics_points;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -475,7 +512,7 @@ fn showProjectSettings(self: *MainScreen) !void {
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(0.5)) });
|
||||
|
||||
{
|
||||
{ // Open notes
|
||||
const btn = ui.textButton("Open notes");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
@ -485,7 +522,7 @@ fn showProjectSettings(self: *MainScreen) !void {
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(0.5)) });
|
||||
|
||||
{
|
||||
{ // Export folder
|
||||
_ = ui.label("Export folder:", .{});
|
||||
if (ui.fileInput(.{
|
||||
.allocator = self.app.allocator,
|
||||
@ -557,6 +594,8 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
|
||||
}
|
||||
}
|
||||
|
||||
_ = ui.label("Statistics:", .{});
|
||||
|
||||
switch (view.reference) {
|
||||
.channel => |channel_id| {
|
||||
const channel = project.channels.get(channel_id).?;
|
||||
@ -588,7 +627,7 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
|
||||
}
|
||||
}
|
||||
|
||||
const sample_list_id = project.getViewSampleListId(view_id);
|
||||
const sample_list_id = project.getViewReferenceSampleListId(view_id);
|
||||
const sample_list = project.sample_lists.get(sample_list_id).?;
|
||||
const sample_count = sample_list.getLength();
|
||||
|
||||
@ -607,122 +646,122 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
|
||||
|
||||
_ = ui.label("Duration: {s}", .{ duration_str orelse "-" });
|
||||
|
||||
var deferred_remove: std.BoundedArray(usize, App.View.max_transforms) = .{};
|
||||
// var deferred_remove: std.BoundedArray(usize, App.View.max_transforms) = .{};
|
||||
|
||||
for (0.., view.transforms.slice()) |i, *_transform| {
|
||||
const transform: *App.Transform = _transform;
|
||||
// for (0.., view.transforms.slice()) |i, *_transform| {
|
||||
// const transform: *App.Transform = _transform;
|
||||
|
||||
const row = ui.createBox(.{
|
||||
.key = UI.Key.initPtr(transform),
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initFixedPixels(ui.rem(1.5)),
|
||||
.layout_direction = .left_to_right
|
||||
});
|
||||
row.beginChildren();
|
||||
defer row.endChildren();
|
||||
// const row = ui.createBox(.{
|
||||
// .key = UI.Key.initPtr(transform),
|
||||
// .size_x = UI.Sizing.initGrowFull(),
|
||||
// .size_y = UI.Sizing.initFixedPixels(ui.rem(1.5)),
|
||||
// .layout_direction = .left_to_right
|
||||
// });
|
||||
// row.beginChildren();
|
||||
// defer row.endChildren();
|
||||
|
||||
if (ui.signal(ui.textButton("Remove")).clicked()) {
|
||||
deferred_remove.appendAssumeCapacity(i);
|
||||
}
|
||||
// if (ui.signal(ui.textButton("Remove")).clicked()) {
|
||||
// deferred_remove.appendAssumeCapacity(i);
|
||||
// }
|
||||
|
||||
{
|
||||
const options = .{
|
||||
.{ .multiply, "Multiply" },
|
||||
.{ .addition, "Addition" },
|
||||
.{ .sliding_window, "Sliding window" },
|
||||
};
|
||||
// {
|
||||
// const options = .{
|
||||
// .{ .multiply, "Multiply" },
|
||||
// .{ .addition, "Addition" },
|
||||
// .{ .sliding_window, "Sliding window" },
|
||||
// };
|
||||
|
||||
const select = ui.button(ui.keyFromString("Transform select"));
|
||||
select.setFmtText("{s}", .{switch (transform.*) {
|
||||
.sliding_window => "Sliding window",
|
||||
.addition => "Addition",
|
||||
.multiply => "Multiply"
|
||||
}});
|
||||
// const select = ui.button(ui.keyFromString("Transform select"));
|
||||
// select.setFmtText("{s}", .{switch (transform.*) {
|
||||
// .sliding_window => "Sliding window",
|
||||
// .addition => "Addition",
|
||||
// .multiply => "Multiply"
|
||||
// }});
|
||||
|
||||
select.size.y = UI.Sizing.initGrowFull();
|
||||
select.alignment.y = .center;
|
||||
if (ui.signal(select).clicked()) {
|
||||
select.persistent.open = !select.persistent.open;
|
||||
}
|
||||
// select.size.y = UI.Sizing.initGrowFull();
|
||||
// select.alignment.y = .center;
|
||||
// if (ui.signal(select).clicked()) {
|
||||
// select.persistent.open = !select.persistent.open;
|
||||
// }
|
||||
|
||||
if (select.persistent.open) {
|
||||
const popup = ui.createBox(.{
|
||||
.key = ui.keyFromString("Select 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();
|
||||
// if (select.persistent.open) {
|
||||
// const popup = ui.createBox(.{
|
||||
// .key = ui.keyFromString("Select 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();
|
||||
|
||||
inline for (options) |option| {
|
||||
const select_option = ui.textButton(option[1]);
|
||||
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;
|
||||
// inline for (options) |option| {
|
||||
// const select_option = ui.textButton(option[1]);
|
||||
// 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;
|
||||
transform.* = switch (option[0]) {
|
||||
.sliding_window => App.Transform{ .sliding_window = sample_rate orelse 0 },
|
||||
.addition => App.Transform{ .addition = 0 },
|
||||
.multiply => App.Transform{ .multiply = 1 },
|
||||
else => unreachable
|
||||
};
|
||||
}
|
||||
}
|
||||
// const signal = ui.signal(select_option);
|
||||
// if (signal.clicked()) {
|
||||
// select.persistent.open = false;
|
||||
// transform.* = switch (option[0]) {
|
||||
// .sliding_window => App.Transform{ .sliding_window = sample_rate orelse 0 },
|
||||
// .addition => App.Transform{ .addition = 0 },
|
||||
// .multiply => App.Transform{ .multiply = 1 },
|
||||
// else => unreachable
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
_ = ui.signal(popup);
|
||||
}
|
||||
}
|
||||
// _ = ui.signal(popup);
|
||||
// }
|
||||
// }
|
||||
|
||||
var input_opts = UI.NumberInputOptions{
|
||||
.key = ui.keyFromString("Sliding window"),
|
||||
.storage = &self.transform_inputs[i],
|
||||
.width = ui.rem(4)
|
||||
// .postfix = if (sample_rate != null) " s" else null,
|
||||
// .display_scalar = sample_rate,
|
||||
};
|
||||
// var input_opts = UI.NumberInputOptions{
|
||||
// .key = ui.keyFromString("Sliding window"),
|
||||
// .storage = &self.transform_inputs[i],
|
||||
// .width = ui.rem(4)
|
||||
// // .postfix = if (sample_rate != null) " s" else null,
|
||||
// // .display_scalar = sample_rate,
|
||||
// };
|
||||
|
||||
_ = ui.createBox(.{ .size_x = UI.Sizing.initGrowFull() });
|
||||
// _ = ui.createBox(.{ .size_x = UI.Sizing.initGrowFull() });
|
||||
|
||||
if (transform.* == .sliding_window and sample_rate != null) {
|
||||
input_opts.postfix = " s";
|
||||
input_opts.display_scalar = sample_rate;
|
||||
}
|
||||
// if (transform.* == .sliding_window and sample_rate != null) {
|
||||
// input_opts.postfix = " s";
|
||||
// input_opts.display_scalar = sample_rate;
|
||||
// }
|
||||
|
||||
const current_value = switch (transform.*) {
|
||||
.sliding_window => |*v| v,
|
||||
.addition => |*v| v,
|
||||
.multiply => |*v| v
|
||||
};
|
||||
input_opts.initial = current_value.*;
|
||||
// const current_value = switch (transform.*) {
|
||||
// .sliding_window => |*v| v,
|
||||
// .addition => |*v| v,
|
||||
// .multiply => |*v| v
|
||||
// };
|
||||
// input_opts.initial = current_value.*;
|
||||
|
||||
const new_value = try ui.numberInput(f64, input_opts);
|
||||
if (new_value != null) {
|
||||
current_value.* = new_value.?;
|
||||
}
|
||||
}
|
||||
// const new_value = try ui.numberInput(f64, input_opts);
|
||||
// if (new_value != null) {
|
||||
// current_value.* = new_value.?;
|
||||
// }
|
||||
// }
|
||||
|
||||
for (0..deferred_remove.len) |i| {
|
||||
const transform_index = deferred_remove.get(deferred_remove.len - 1 - i);
|
||||
_ = view.transforms.orderedRemove(transform_index);
|
||||
}
|
||||
// for (0..deferred_remove.len) |i| {
|
||||
// const transform_index = deferred_remove.get(deferred_remove.len - 1 - i);
|
||||
// _ = view.transforms.orderedRemove(transform_index);
|
||||
// }
|
||||
|
||||
if (view.transforms.unusedCapacitySlice().len > 0) {
|
||||
const btn = ui.textButton("Add transform");
|
||||
if (ui.signal(btn).clicked()) {
|
||||
view.transforms.appendAssumeCapacity(.{ .addition = 0 });
|
||||
}
|
||||
}
|
||||
// if (view.transforms.unusedCapacitySlice().len > 0) {
|
||||
// const btn = ui.textButton("Add transform");
|
||||
// if (ui.signal(btn).clicked()) {
|
||||
// view.transforms.appendAssumeCapacity(.{ .addition = 0 });
|
||||
// }
|
||||
// }
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initGrowFull() });
|
||||
|
||||
@ -735,103 +774,103 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn showMarkedRange(self: *MainScreen, view_id: Id, index: usize) void {
|
||||
var ui = &self.app.ui;
|
||||
// fn showMarkedRange(self: *MainScreen, view_id: Id, index: usize) void {
|
||||
// var ui = &self.app.ui;
|
||||
|
||||
const view = self.app.getView(view_id) orelse return;
|
||||
// const view = self.app.getView(view_id) orelse return;
|
||||
|
||||
const marked_range = view.marked_ranges.get(index);
|
||||
// const marked_range = view.marked_ranges.get(index);
|
||||
|
||||
|
||||
{
|
||||
const label = ui.label("Selected range", .{});
|
||||
label.borders.bottom = .{
|
||||
.color = srcery.blue,
|
||||
.size = 1
|
||||
};
|
||||
// {
|
||||
// const label = ui.label("Selected range", .{});
|
||||
// label.borders.bottom = .{
|
||||
// .color = srcery.blue,
|
||||
// .size = 1
|
||||
// };
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
}
|
||||
// _ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
// }
|
||||
|
||||
const sample_rate = self.app.project.getSampleRate();
|
||||
if (marked_range.axis == .X and sample_rate != null) {
|
||||
_ = ui.label("From: {d:.3}s", .{ marked_range.range.lower / sample_rate.? });
|
||||
_ = ui.label("To: {d:.3}s", .{ marked_range.range.upper / sample_rate.? });
|
||||
_ = ui.label("Size: {d:.3}s", .{ marked_range.range.size() / sample_rate.? });
|
||||
} else {
|
||||
_ = ui.label("From: {d:.2}", .{ marked_range.range.lower });
|
||||
_ = ui.label("To: {d:.2}", .{ marked_range.range.upper });
|
||||
_ = ui.label("Size: {d:.2}", .{ marked_range.range.size() });
|
||||
}
|
||||
// const sample_rate = self.app.project.getSampleRate();
|
||||
// if (marked_range.axis == .X and sample_rate != null) {
|
||||
// _ = ui.label("From: {d:.3}s", .{ marked_range.range.lower / sample_rate.? });
|
||||
// _ = ui.label("To: {d:.3}s", .{ marked_range.range.upper / sample_rate.? });
|
||||
// _ = ui.label("Size: {d:.3}s", .{ marked_range.range.size() / sample_rate.? });
|
||||
// } else {
|
||||
// _ = ui.label("From: {d:.2}", .{ marked_range.range.lower });
|
||||
// _ = ui.label("To: {d:.2}", .{ marked_range.range.upper });
|
||||
// _ = ui.label("Size: {d:.2}", .{ marked_range.range.size() });
|
||||
// }
|
||||
|
||||
_ = ui.label("Samples: {d:.2}", .{ marked_range.range.size() });
|
||||
// _ = ui.label("Samples: {d:.2}", .{ marked_range.range.size() });
|
||||
|
||||
if (marked_range.axis == .X) {
|
||||
if (marked_range.min) |min| {
|
||||
_ = ui.label("Minimum: {d:.3}", .{ min });
|
||||
} else{
|
||||
_ = ui.label("Minimum: <unknown>", .{});
|
||||
}
|
||||
// if (marked_range.axis == .X) {
|
||||
// if (marked_range.min) |min| {
|
||||
// _ = ui.label("Minimum: {d:.3}", .{ min });
|
||||
// } else{
|
||||
// _ = ui.label("Minimum: <unknown>", .{});
|
||||
// }
|
||||
|
||||
if (marked_range.max) |max| {
|
||||
_ = ui.label("Maximum: {d:.3}", .{ max });
|
||||
} else{
|
||||
_ = ui.label("Maximum: <unknown>", .{});
|
||||
}
|
||||
// if (marked_range.max) |max| {
|
||||
// _ = ui.label("Maximum: {d:.3}", .{ max });
|
||||
// } else{
|
||||
// _ = ui.label("Maximum: <unknown>", .{});
|
||||
// }
|
||||
|
||||
if (marked_range.average) |average| {
|
||||
_ = ui.label("Average: {d:.3}", .{ average });
|
||||
} else{
|
||||
_ = ui.label("Average: <unknown>", .{});
|
||||
}
|
||||
// if (marked_range.average) |average| {
|
||||
// _ = ui.label("Average: {d:.3}", .{ average });
|
||||
// } else{
|
||||
// _ = ui.label("Average: <unknown>", .{});
|
||||
// }
|
||||
|
||||
if (marked_range.standard_deviation) |standard_deviation| {
|
||||
_ = ui.label("Standard deviation: {d:.3}", .{ standard_deviation });
|
||||
} else{
|
||||
_ = ui.label("Standard deviation: <unknown>", .{});
|
||||
}
|
||||
}
|
||||
// if (marked_range.standard_deviation) |standard_deviation| {
|
||||
// _ = ui.label("Standard deviation: {d:.3}", .{ standard_deviation });
|
||||
// } else{
|
||||
// _ = ui.label("Standard deviation: <unknown>", .{});
|
||||
// }
|
||||
// }
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initGrowFull() });
|
||||
// _ = ui.createBox(.{ .size_y = UI.Sizing.initGrowFull() });
|
||||
|
||||
{
|
||||
const btn = ui.textButton("Remove");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.red, .size = 4 });
|
||||
const signal = ui.signal(btn);
|
||||
if (signal.clicked() or ui.isKeyboardPressed(.key_backspace)) {
|
||||
self.view_controls.show_marked_range = null;
|
||||
_ = view.marked_ranges.swapRemove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
// {
|
||||
// const btn = ui.textButton("Remove");
|
||||
// btn.borders = UI.Borders.all(.{ .color = srcery.red, .size = 4 });
|
||||
// const signal = ui.signal(btn);
|
||||
// if (signal.clicked() or ui.isKeyboardPressed(.key_backspace)) {
|
||||
// self.side_panel = .project;
|
||||
// _ = view.marked_ranges.swapRemove(index);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn showMarker(self: *MainScreen, view_id: Id, index: usize) void {
|
||||
var ui = &self.app.ui;
|
||||
// fn showMarker(self: *MainScreen, view_id: Id, index: usize) void {
|
||||
// var ui = &self.app.ui;
|
||||
|
||||
const view = self.app.getView(view_id) orelse return;
|
||||
// const view = self.app.getView(view_id) orelse return;
|
||||
|
||||
const marker = view.markers.get(index);
|
||||
// const marker = view.markers.get(index);
|
||||
|
||||
{
|
||||
const label = ui.label("Selected range", .{});
|
||||
label.borders.bottom = .{
|
||||
.color = srcery.blue,
|
||||
.size = 1
|
||||
};
|
||||
// {
|
||||
// const label = ui.label("Selected range", .{});
|
||||
// label.borders.bottom = .{
|
||||
// .color = srcery.blue,
|
||||
// .size = 1
|
||||
// };
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
}
|
||||
// _ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
// }
|
||||
|
||||
const sample_rate = self.app.project.getSampleRate();
|
||||
// const sample_rate = self.app.project.getSampleRate();
|
||||
|
||||
if (sample_rate != null) {
|
||||
const duration = utils.formatDuration(ui.frameAllocator(), marker / sample_rate.?) catch "";
|
||||
_ = ui.label("Position: {s}", .{ duration });
|
||||
} else {
|
||||
_ = ui.label("Position: {d:.2}", .{ marker });
|
||||
}
|
||||
// if (sample_rate != null) {
|
||||
// const duration = utils.formatDuration(ui.frameAllocator(), marker / sample_rate.?) catch "";
|
||||
// _ = ui.label("Position: {s}", .{ duration });
|
||||
// } else {
|
||||
// _ = ui.label("Position: {d:.2}", .{ marker });
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
fn showToolbar(self: *MainScreen) void {
|
||||
var ui = &self.app.ui;
|
||||
@ -945,38 +984,38 @@ fn showToolbar(self: *MainScreen) void {
|
||||
|
||||
_ = ui.createBox(.{ .size_x = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
|
||||
{
|
||||
var btn = ui.textButton("Move");
|
||||
if (self.view_controls.selected_tool == .move) {
|
||||
btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
||||
}
|
||||
// {
|
||||
// var btn = ui.textButton("Move");
|
||||
// if (self.view_controls.selected_tool == .move) {
|
||||
// btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
||||
// }
|
||||
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.view_controls.selected_tool = .move;
|
||||
}
|
||||
}
|
||||
// if (ui.signal(btn).clicked()) {
|
||||
// self.view_controls.selected_tool = .move;
|
||||
// }
|
||||
// }
|
||||
|
||||
{
|
||||
var btn = ui.textButton("Select");
|
||||
if (self.view_controls.selected_tool == .select) {
|
||||
btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
||||
}
|
||||
// {
|
||||
// var btn = ui.textButton("Select");
|
||||
// if (self.view_controls.selected_tool == .select) {
|
||||
// btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
||||
// }
|
||||
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.view_controls.selected_tool = .select;
|
||||
}
|
||||
}
|
||||
// if (ui.signal(btn).clicked()) {
|
||||
// self.view_controls.selected_tool = .select;
|
||||
// }
|
||||
// }
|
||||
|
||||
{
|
||||
var btn = ui.textButton("Marker");
|
||||
if (self.view_controls.selected_tool == .marker) {
|
||||
btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
||||
}
|
||||
// {
|
||||
// var btn = ui.textButton("Marker");
|
||||
// if (self.view_controls.selected_tool == .marker) {
|
||||
// btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
||||
// }
|
||||
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.view_controls.selected_tool = .marker;
|
||||
}
|
||||
}
|
||||
// if (ui.signal(btn).clicked()) {
|
||||
// self.view_controls.selected_tool = .marker;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn showSolutions(self: *MainScreen) !void {
|
||||
@ -1024,15 +1063,14 @@ fn showSolutions(self: *MainScreen) !void {
|
||||
const btn = ui.textButton("Close");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.red, .size = 4 });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.view_solutions = false;
|
||||
self.side_panel = .project;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn showStatisticsPoints(self: *MainScreen) !void {
|
||||
var ui = &self.app.ui;
|
||||
const ui = &self.app.ui;
|
||||
const project = &self.app.project;
|
||||
_ = &ui;
|
||||
|
||||
{
|
||||
const label = ui.label("Statistics points", .{});
|
||||
@ -1070,7 +1108,72 @@ fn showStatisticsPoints(self: *MainScreen) !void {
|
||||
const btn = ui.textButton("Close");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.red, .size = 4 });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.view_statistics_points = false;
|
||||
self.side_panel = .project;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn showGainChanges(self: *MainScreen) !void {
|
||||
const project = &self.app.project;
|
||||
var ui = &self.app.ui;
|
||||
|
||||
{
|
||||
const label = ui.label("Gain changes", .{});
|
||||
label.borders.bottom = .{
|
||||
.color = srcery.white,
|
||||
.size = 1
|
||||
};
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
}
|
||||
|
||||
const sample_rate = project.getSampleRate();
|
||||
|
||||
var removed_index: ?usize = null;
|
||||
|
||||
for (0.., project.gain_changes.slice()) |i, *gain_change| {
|
||||
const row = ui.createBox(.{
|
||||
.key = UI.Key.initPtr(gain_change),
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initFitChildren(),
|
||||
.layout_direction = .left_to_right,
|
||||
.align_y = .center,
|
||||
.layout_gap = ui.rem(1)
|
||||
});
|
||||
row.beginChildren();
|
||||
defer row.endChildren();
|
||||
|
||||
if (sample_rate != null) {
|
||||
const seconds = gain_change.sample / sample_rate.?;
|
||||
_ = ui.label("{s}", .{ try utils.formatDuration(ui.frameAllocator(), seconds) });
|
||||
} else {
|
||||
_ = ui.label("{d}", .{ gain_change.sample });
|
||||
}
|
||||
|
||||
var gain_label = ui.label("{d:.3}", .{ gain_change.gain });
|
||||
gain_label.size.x = UI.Sizing.initGrowFull();
|
||||
|
||||
if (i > 0) {
|
||||
const btn = ui.textButton("Remove");
|
||||
btn.background = srcery.bright_red;
|
||||
btn.padding = UI.Padding.all(ui.rem(0.2));
|
||||
if (ui.signal(btn).clicked()) {
|
||||
removed_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (removed_index) |index| {
|
||||
project.removeGainChange(index);
|
||||
}
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
|
||||
{
|
||||
const btn = ui.textButton("Close");
|
||||
btn.borders = UI.Borders.all(.{ .color = srcery.red, .size = 4 });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.side_panel = .project;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1093,18 +1196,28 @@ pub fn showSidePanel(self: *MainScreen) !void {
|
||||
|
||||
_ = ui.createBox(.{ .size_x = UI.Sizing.initFixedPixels(ui.rem(18)) });
|
||||
|
||||
if (self.view_solutions) {
|
||||
try self.showSolutions();
|
||||
} else if (self.view_controls.show_marker) |marker| {
|
||||
self.showMarker(marker.view_id, marker.index);
|
||||
} else if (self.view_controls.show_marked_range) |show_marked_range| {
|
||||
self.showMarkedRange(show_marked_range.view_id, show_marked_range.index);
|
||||
} else if (self.view_controls.view_settings) |view_id| {
|
||||
try self.showViewSettings(view_id);
|
||||
} else if (self.view_statistics_points) {
|
||||
try self.showStatisticsPoints();
|
||||
} else {
|
||||
try self.showProjectSettings();
|
||||
switch (self.side_panel) {
|
||||
.project => {
|
||||
try self.showProjectSettings();
|
||||
},
|
||||
.solutions => {
|
||||
try self.showSolutions();
|
||||
},
|
||||
.statistics_points => {
|
||||
try self.showStatisticsPoints();
|
||||
},
|
||||
.gain_changes => {
|
||||
try self.showGainChanges();
|
||||
},
|
||||
.view_settings => |view_id| {
|
||||
try self.showViewSettings(view_id);
|
||||
},
|
||||
// .marker => |args| {
|
||||
// self.showMarker(args.view_id, args.index);
|
||||
// },
|
||||
// .marked_range => |args| {
|
||||
// self.showMarkedRange(args.view_id, args.index);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1199,14 +1312,8 @@ pub fn tick(self: *MainScreen) !void {
|
||||
if (ui.isKeyboardPressed(.key_escape)) {
|
||||
if (self.modal != null) {
|
||||
self.modal = null;
|
||||
} else if (self.view_controls.view_settings != null) {
|
||||
self.view_controls.view_settings = null;
|
||||
} else if (self.view_controls.show_marked_range != null) {
|
||||
self.view_controls.show_marked_range = null;
|
||||
} else if (self.view_solutions) {
|
||||
self.view_solutions = false;
|
||||
} else if (self.view_statistics_points) {
|
||||
self.view_statistics_points = false;
|
||||
} else if (self.side_panel != .project) {
|
||||
self.side_panel = .project;
|
||||
} else {
|
||||
self.app.should_close = true;
|
||||
}
|
||||
|
13
src/ui.zig
13
src/ui.zig
@ -2779,8 +2779,9 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||
storage.editing = true;
|
||||
}
|
||||
|
||||
var stop_editing = false;
|
||||
if (self.isKeyActiveAny() and !container_signal.active) {
|
||||
storage.editing = false;
|
||||
stop_editing = true;
|
||||
}
|
||||
|
||||
// Text input controls
|
||||
@ -2952,7 +2953,7 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||
}
|
||||
|
||||
if (self.isKeyboardPressed(.key_escape)) {
|
||||
storage.editing = false;
|
||||
stop_editing = true;
|
||||
}
|
||||
|
||||
if (self.isKeyboardPressed(.key_enter)) {
|
||||
@ -2975,9 +2976,15 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||
}
|
||||
|
||||
if (!opts.editable) {
|
||||
storage.editing = false;
|
||||
stop_editing = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (stop_editing) {
|
||||
storage.editing = false;
|
||||
storage.cursor_start = 0;
|
||||
storage.cursor_stop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn numberInput(self: *UI, T: type, opts: NumberInputOptions) !?T {
|
||||
|
Loading…
Reference in New Issue
Block a user