add basic UI for adding and removing transforms
This commit is contained in:
parent
b9e0a7a2d6
commit
2545774575
384
src/app.zig
384
src/app.zig
@ -166,6 +166,123 @@ fn GenerationalArray(Item: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SumRingBuffer = struct {
|
||||||
|
buffer: []f64,
|
||||||
|
len: usize = 0,
|
||||||
|
last_index: usize = 0,
|
||||||
|
|
||||||
|
sum: f64 = 0,
|
||||||
|
|
||||||
|
pub fn init(buffer: []f64) SumRingBuffer {
|
||||||
|
return SumRingBuffer{
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(self: *SumRingBuffer, sample: f64) void {
|
||||||
|
if (self.len < self.buffer.len) {
|
||||||
|
self.buffer[self.len] = sample;
|
||||||
|
self.len += 1;
|
||||||
|
} else {
|
||||||
|
self.sum -= self.buffer[self.last_index];
|
||||||
|
self.buffer[self.last_index] = sample;
|
||||||
|
self.last_index = @mod(self.last_index + 1, self.buffer.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sum += sample;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 fn apply(self: Transform, state: *State, destination: []f64, source: []f64) void {
|
||||||
|
switch (self) {
|
||||||
|
.sliding_window => {
|
||||||
|
const sum_ring_buffer = &(state.sum_ring_buffer.?);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
destination[i] = sum_ring_buffer.sum / @as(f64, @floatFromInt(sum_ring_buffer.len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
.multiply => |scalar| {
|
||||||
|
for (0..source.len) |i| {
|
||||||
|
destination[i] = scalar * source[i];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.addition => |offset| {
|
||||||
|
for (0..source.len) |i| {
|
||||||
|
destination[i] = offset + source[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eqlSlice(slice1: []const Transform, slice2: []const Transform) bool {
|
||||||
|
if (slice1.len != slice2.len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (0.., slice1) |i, transform1| {
|
||||||
|
if (!std.meta.eql(transform1, slice2[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const SampleList = struct {
|
pub const SampleList = struct {
|
||||||
pub const Block = struct {
|
pub const Block = struct {
|
||||||
pub const Id = usize;
|
pub const Id = usize;
|
||||||
@ -348,6 +465,20 @@ pub const SampleList = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn appendToBlock(self: *SampleList, block: *Block, samples: []const f64) usize {
|
||||||
|
const appended = block.append(samples);
|
||||||
|
|
||||||
|
if (block.min) |block_min| {
|
||||||
|
self.min = @min(self.min orelse block_min, block_min);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.max) |block_max| {
|
||||||
|
self.max = @max(self.max orelse block_max, block_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return appended;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn append(self: *SampleList, samples: []const f64) !void {
|
pub fn append(self: *SampleList, samples: []const f64) !void {
|
||||||
if (samples.len == 0) return;
|
if (samples.len == 0) return;
|
||||||
|
|
||||||
@ -358,15 +489,7 @@ pub const SampleList = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const last_block = &self.blocks.items[self.blocks.items.len - 1];
|
const last_block = &self.blocks.items[self.blocks.items.len - 1];
|
||||||
appended_count += last_block.append(samples[appended_count..]);
|
appended_count += self.appendToBlock(last_block, samples[appended_count..]);
|
||||||
|
|
||||||
if (last_block.min) |block_min| {
|
|
||||||
self.min = @min(self.min orelse block_min, block_min);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (last_block.max) |block_max| {
|
|
||||||
self.max = @max(self.max orelse block_max, block_max);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +540,62 @@ 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
var sample_list = SampleList.init(std.testing.allocator);
|
var sample_list = SampleList.init(std.testing.allocator);
|
||||||
defer sample_list.deinit();
|
defer sample_list.deinit();
|
||||||
@ -596,6 +775,9 @@ pub const File = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const View = struct {
|
pub const View = struct {
|
||||||
|
pub const max_transforms = 16;
|
||||||
|
pub const BoundedTransformsArray = std.BoundedArray(Transform, max_transforms);
|
||||||
|
|
||||||
pub const MarkedRange = struct {
|
pub const MarkedRange = struct {
|
||||||
// Persistent
|
// Persistent
|
||||||
axis: UI.Axis,
|
axis: UI.Axis,
|
||||||
@ -698,7 +880,7 @@ pub const View = struct {
|
|||||||
graph_opts: Graph.ViewOptions = .{},
|
graph_opts: Graph.ViewOptions = .{},
|
||||||
sync_controls: bool = false,
|
sync_controls: bool = false,
|
||||||
marked_ranges: std.BoundedArray(MarkedRange, 32) = .{},
|
marked_ranges: std.BoundedArray(MarkedRange, 32) = .{},
|
||||||
sliding_window: ?f64 = null,
|
transforms: BoundedTransformsArray = .{},
|
||||||
|
|
||||||
// Runtime
|
// Runtime
|
||||||
graph_cache: Graph.RenderCache = .{},
|
graph_cache: Graph.RenderCache = .{},
|
||||||
@ -707,6 +889,7 @@ pub const View = struct {
|
|||||||
unit: ?NIDaq.Unit = .Voltage,
|
unit: ?NIDaq.Unit = .Voltage,
|
||||||
|
|
||||||
transformed_samples: ?Id = null,
|
transformed_samples: ?Id = null,
|
||||||
|
computed_transforms: BoundedTransformsArray = .{},
|
||||||
|
|
||||||
pub fn clear(self: *View) void {
|
pub fn clear(self: *View) void {
|
||||||
self.graph_cache.clear();
|
self.graph_cache.clear();
|
||||||
@ -1222,10 +1405,6 @@ pub const Command = union(enum) {
|
|||||||
start_output: Id, // Channel id
|
start_output: Id, // Channel id
|
||||||
add_file_from_picker,
|
add_file_from_picker,
|
||||||
reload_file: Id, // File id
|
reload_file: Id, // File id
|
||||||
update_sliding_window: struct {
|
|
||||||
view_id: Id,
|
|
||||||
sliding_window: ?f64
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CollectionTask = struct {
|
pub const CollectionTask = struct {
|
||||||
@ -1243,12 +1422,13 @@ pub const CollectionTask = struct {
|
|||||||
const WorkJob = struct {
|
const WorkJob = struct {
|
||||||
const Stage = enum {
|
const Stage = enum {
|
||||||
init,
|
init,
|
||||||
calculate_blocks
|
launch_threads,
|
||||||
|
finished
|
||||||
};
|
};
|
||||||
|
|
||||||
view_id: Id,
|
view_id: Id,
|
||||||
stage: Stage = .init,
|
stage: Stage = .init,
|
||||||
sliding_window: ?f64 = null,
|
transforms: View.BoundedTransformsArray = .{},
|
||||||
|
|
||||||
mutex: std.Thread.Mutex = .{},
|
mutex: std.Thread.Mutex = .{},
|
||||||
running_thread_jobs: std.ArrayListUnmanaged(SampleList.Block.Id) = .{},
|
running_thread_jobs: std.ArrayListUnmanaged(SampleList.Block.Id) = .{},
|
||||||
@ -1289,18 +1469,26 @@ const WorkJob = struct {
|
|||||||
self.running_thread_jobs.deinit(allocator);
|
self.running_thread_jobs.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(self: *WorkJob, id: Id, app: *App) !bool {
|
pub fn update(self: *WorkJob, app: *App) !bool {
|
||||||
|
if (self.stage == .finished) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const project = &app.project;
|
const project = &app.project;
|
||||||
const view = project.views.get(self.view_id) orelse return true;
|
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.getViewSampleListId(self.view_id);
|
||||||
const sample_list = project.sample_lists.get(sample_list_id).?;
|
const sample_list = project.sample_lists.get(sample_list_id).?;
|
||||||
|
|
||||||
if (view.sliding_window != self.sliding_window) return true;
|
const transforms = self.transforms.constSlice();
|
||||||
|
if (!Transform.eqlSlice(view.transforms.constSlice(), transforms)) {
|
||||||
|
// Transforms changed, job needs to be cancelled.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
switch (self.stage) {
|
switch (self.stage) {
|
||||||
.init => {
|
.init => {
|
||||||
if (self.sliding_window == null) {
|
if (transforms.len == 0) {
|
||||||
if (view.transformed_samples) |transformed_samples_id| {
|
if (view.transformed_samples) |transformed_samples_id| {
|
||||||
project.removeSampleList(transformed_samples_id);
|
project.removeSampleList(transformed_samples_id);
|
||||||
}
|
}
|
||||||
@ -1314,29 +1502,51 @@ const WorkJob = struct {
|
|||||||
const transformed_samples = project.sample_lists.get(view.transformed_samples.?).?;
|
const transformed_samples = project.sample_lists.get(view.transformed_samples.?).?;
|
||||||
try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len);
|
try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len);
|
||||||
|
|
||||||
self.stage = .calculate_blocks;
|
self.stage = .launch_threads;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.calculate_blocks => {
|
.launch_threads => {
|
||||||
const max_block_to_process = 32;
|
const max_block_to_process = 256;
|
||||||
|
|
||||||
while (self.getRunningThreadCount() < app.work_thread_pool.threads.len and self.processed_up_to < sample_list.blocks.items.len) {
|
while (self.getRunningThreadCount() < app.work_thread_pool.threads.len and self.processed_up_to < sample_list.blocks.items.len) {
|
||||||
const block_id = self.processed_up_to;
|
const block_id = self.processed_up_to;
|
||||||
const block_count = @min(sample_list.blocks.items.len - self.processed_up_to, max_block_to_process);
|
const block_count = @min(sample_list.blocks.items.len - self.processed_up_to, max_block_to_process);
|
||||||
self.processed_up_to += block_count;
|
self.processed_up_to += block_count;
|
||||||
|
|
||||||
try app.work_thread_pool.spawn(transformedSamplesWorker, .{ app, id, block_id, block_count });
|
try app.work_thread_pool.spawn(workThread, .{ self, project, block_id, block_count });
|
||||||
try self.appendRunningThread(app.allocator, block_id);
|
try self.appendRunningThread(app.allocator, block_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.processed_up_to == sample_list.blocks.items.len and self.getRunningThreadCount() == 0) {
|
if (self.processed_up_to == sample_list.blocks.items.len) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
.finished => unreachable
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn workThread(self: *WorkJob, project: *Project, from_block_id: SampleList.Block.Id, block_count: SampleList.Block.Len) void {
|
||||||
|
defer self.removeRunningThread(from_block_id);
|
||||||
|
|
||||||
|
// var timer = std.time.Timer.start() catch unreachable;
|
||||||
|
// defer {
|
||||||
|
// const duration = timer.read();
|
||||||
|
// std.debug.print("finished {d:.5}ms\n", .{ @as(f64, @floatFromInt(duration)) / std.time.ns_per_ms });
|
||||||
|
// }
|
||||||
|
|
||||||
|
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 sample_list_id = project.getViewSampleListId(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});
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
@ -1643,31 +1853,39 @@ pub fn tick(self: *App) !void {
|
|||||||
self.loadFile(file_id) catch |e| {
|
self.loadFile(file_id) catch |e| {
|
||||||
log.err("Failed to load file: {}", .{ e });
|
log.err("Failed to load file: {}", .{ e });
|
||||||
};
|
};
|
||||||
},
|
|
||||||
.update_sliding_window => |args| {
|
|
||||||
const view = self.project.views.get(args.view_id) orelse continue;
|
|
||||||
view.sliding_window = args.sliding_window;
|
|
||||||
|
|
||||||
_ = self.work_jobs.insert(WorkJob{
|
|
||||||
.view_id = args.view_id,
|
|
||||||
.sliding_window = args.sliding_window
|
|
||||||
}) catch |e| {
|
|
||||||
log.err("Failed to create a work job: {}", .{ e });
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var view_iter = self.project.views.idIterator();
|
||||||
|
while (view_iter.next()) |view_id| {
|
||||||
|
const view = self.project.views.get(view_id).?;
|
||||||
|
|
||||||
|
if (Transform.eqlSlice(view.computed_transforms.constSlice(), view.transforms.constSlice())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var work_job_iter = self.work_jobs.idIterator();
|
var work_job_iter = self.work_jobs.idIterator();
|
||||||
while (work_job_iter.next()) |work_job_id| {
|
while (work_job_iter.next()) |work_job_id| {
|
||||||
const work_job = self.work_jobs.get(work_job_id).?;
|
const work_job = self.work_jobs.get(work_job_id).?;
|
||||||
const job_done = try work_job.update(work_job_id, self);
|
const job_done = try work_job.update(self);
|
||||||
|
|
||||||
if (job_done) {
|
if (job_done) {
|
||||||
|
work_job.stage = .finished;
|
||||||
if (work_job.getRunningThreadCount() == 0) {
|
if (work_job.getRunningThreadCount() == 0) {
|
||||||
// std.debug.print("job done {}\n", .{work_job_id});
|
std.debug.print("job done {}\n", .{work_job_id});
|
||||||
work_job.deinit(self.allocator);
|
work_job.deinit(self.allocator);
|
||||||
self.work_jobs.remove(work_job_id);
|
self.work_jobs.remove(work_job_id);
|
||||||
}
|
}
|
||||||
@ -1676,94 +1894,6 @@ pub fn tick(self: *App) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transformedSamplesWorker(self: *App, work_job_id: Id, starting_block_id: SampleList.Block.Id, block_count: usize) void {
|
|
||||||
const work_job = self.work_jobs.get(work_job_id) orelse return;
|
|
||||||
defer work_job.removeRunningThread(starting_block_id);
|
|
||||||
|
|
||||||
const allocator = self.allocator;
|
|
||||||
var timer = std.time.Timer.start() catch unreachable;
|
|
||||||
|
|
||||||
const view = self.project.views.get(work_job.view_id) orelse return;
|
|
||||||
const transformed_samples_id = view.transformed_samples orelse return;
|
|
||||||
const transformed_samples = self.project.sample_lists.get(transformed_samples_id) orelse return;
|
|
||||||
const sliding_window_f64: f64 = @ceil(view.sliding_window.?);
|
|
||||||
const sliding_window: usize = @intFromFloat(sliding_window_f64);
|
|
||||||
|
|
||||||
const sample_list_id = self.project.getViewSampleListId(work_job.view_id);
|
|
||||||
const sample_list = self.project.sample_lists.get(sample_list_id) orelse return;
|
|
||||||
|
|
||||||
var running_sum: f64 = 0;
|
|
||||||
var last_samples = std.ArrayList(f64).init(allocator);
|
|
||||||
defer last_samples.deinit();
|
|
||||||
last_samples.ensureTotalCapacityPrecise(sliding_window) catch return;
|
|
||||||
|
|
||||||
for (0..@intFromFloat(@ceil(sliding_window_f64/SampleList.Block.capacity))) |block_offset| {
|
|
||||||
if (block_offset >= starting_block_id) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const source_block = sample_list.getBlock(starting_block_id - (block_offset + 1)).?;
|
|
||||||
for (0..source_block.len) |i| {
|
|
||||||
const sample = source_block.buffer[source_block.len - (i + 1)];
|
|
||||||
|
|
||||||
if (last_samples.items.len == last_samples.capacity) {
|
|
||||||
_ = last_samples.orderedRemove(0);
|
|
||||||
}
|
|
||||||
last_samples.appendAssumeCapacity(sample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0..block_count) |i| {
|
|
||||||
const block_id = starting_block_id + i;
|
|
||||||
|
|
||||||
const source_block = sample_list.getBlock(block_id).?;
|
|
||||||
const transformed_block = transformed_samples.getBlock(block_id).?;
|
|
||||||
|
|
||||||
for (0..source_block.len) |j| {
|
|
||||||
const sample = source_block.buffer[j];
|
|
||||||
transformed_block.buffer[j] = sample * 0.1;
|
|
||||||
|
|
||||||
if (last_samples.items.len == last_samples.capacity) {
|
|
||||||
running_sum -= last_samples.orderedRemove(0);
|
|
||||||
}
|
|
||||||
last_samples.appendAssumeCapacity(sample);
|
|
||||||
running_sum += sample;
|
|
||||||
|
|
||||||
transformed_block.buffer[j] = running_sum / @as(f64, @floatFromInt(last_samples.items.len));
|
|
||||||
}
|
|
||||||
transformed_block.len = source_block.len;
|
|
||||||
transformed_block.recomputeMinMax();
|
|
||||||
}
|
|
||||||
|
|
||||||
// for (0..(SampleList.Block.capacity * block_count)) |offset| {
|
|
||||||
// const i = starting_block_id * SampleList.Block.capacity + offset;
|
|
||||||
|
|
||||||
// const transformed_sample = &transformed_samples.getBlock(@divFloor(i, SampleList.Block.capacity)).?.buffer[@mod(i, SampleList.Block.capacity)];
|
|
||||||
|
|
||||||
// if (i >= 3) {
|
|
||||||
// const zero: f64 = 0;
|
|
||||||
// const sample1 = (sample_list.getSample(i) orelse &zero).*;
|
|
||||||
// const sample2 = (sample_list.getSample(i-1) orelse &zero).*;
|
|
||||||
// const sample3 = (sample_list.getSample(i-2) orelse &zero).*;
|
|
||||||
// const sample4 = (sample_list.getSample(i-3) orelse &zero).*;
|
|
||||||
|
|
||||||
// // if (sample_list.getSample(i)) |sample| {
|
|
||||||
// // }
|
|
||||||
// transformed_sample.* = @tan(sample1 + sample2 + sample3 + sample4);
|
|
||||||
// } else {
|
|
||||||
// transformed_sample.* = 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (0..block_count) |i| {
|
|
||||||
// transformed_samples.getBlock(starting_block_id + i).?.len = sample_list.getBlock(starting_block_id + i).?.len;
|
|
||||||
// }
|
|
||||||
|
|
||||||
const duration = timer.read();
|
|
||||||
_ = duration;
|
|
||||||
// std.debug.print("finished {d:.5}ms\n", .{ @as(f64, @floatFromInt(duration)) / std.time.ns_per_ms });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pushCommand(self: *App, command: Command) void {
|
pub fn pushCommand(self: *App, command: Command) void {
|
||||||
self.command_queue.append(command) catch {
|
self.command_queue.append(command) catch {
|
||||||
log.warn("Failed to push a command, ignoring it", .{});
|
log.warn("Failed to push a command, ignoring it", .{});
|
||||||
|
@ -36,20 +36,25 @@ sample_rate_input: UI.TextInputStorage,
|
|||||||
parsed_sample_rate: ?f64 = null,
|
parsed_sample_rate: ?f64 = null,
|
||||||
|
|
||||||
// View settings
|
// View settings
|
||||||
sliding_window_input: UI.TextInputStorage,
|
transform_inputs: [App.View.max_transforms]UI.TextInputStorage,
|
||||||
channel_save_file_picker: ?Platform.FilePickerId = null,
|
channel_save_file_picker: ?Platform.FilePickerId = null,
|
||||||
file_save_file_picker: ?Platform.FilePickerId = null,
|
file_save_file_picker: ?Platform.FilePickerId = null,
|
||||||
|
|
||||||
pub fn init(app: *App) !MainScreen {
|
pub fn init(app: *App) !MainScreen {
|
||||||
const allocator = app.allocator;
|
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{
|
var self = MainScreen{
|
||||||
.app = app,
|
.app = app,
|
||||||
.frequency_input = UI.TextInputStorage.init(allocator),
|
.frequency_input = UI.TextInputStorage.init(allocator),
|
||||||
.amplitude_input = UI.TextInputStorage.init(allocator),
|
.amplitude_input = UI.TextInputStorage.init(allocator),
|
||||||
.sample_rate_input = UI.TextInputStorage.init(allocator),
|
.sample_rate_input = UI.TextInputStorage.init(allocator),
|
||||||
.view_controls = ViewControlsSystem.init(&app.project),
|
.view_controls = ViewControlsSystem.init(&app.project),
|
||||||
.sliding_window_input = UI.TextInputStorage.init(allocator),
|
.transform_inputs = transform_inputs,
|
||||||
.preview_sample_list_id = try app.project.addSampleList(allocator)
|
.preview_sample_list_id = try app.project.addSampleList(allocator)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,7 +68,9 @@ pub fn deinit(self: *MainScreen) void {
|
|||||||
self.frequency_input.deinit();
|
self.frequency_input.deinit();
|
||||||
self.amplitude_input.deinit();
|
self.amplitude_input.deinit();
|
||||||
self.sample_rate_input.deinit();
|
self.sample_rate_input.deinit();
|
||||||
self.sliding_window_input.deinit();
|
for (self.transform_inputs) |input| {
|
||||||
|
input.deinit();
|
||||||
|
}
|
||||||
self.app.project.removeSampleList(self.preview_sample_list_id);
|
self.app.project.removeSampleList(self.preview_sample_list_id);
|
||||||
|
|
||||||
self.clearProtocolErrorMessage();
|
self.clearProtocolErrorMessage();
|
||||||
@ -275,17 +282,12 @@ fn showProjectSettings(self: *MainScreen) !void {
|
|||||||
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
|
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
|
||||||
}
|
}
|
||||||
|
|
||||||
var initial: ?[]const u8 = null;
|
|
||||||
if (project.sample_rate) |selected_sample_rate| {
|
|
||||||
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ selected_sample_rate });
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = ui.label("Sample rate", .{});
|
_ = ui.label("Sample rate", .{});
|
||||||
self.parsed_sample_rate = try ui.numberInput(f64, .{
|
self.parsed_sample_rate = try ui.numberInput(f64, .{
|
||||||
.key = ui.keyFromString("Sample rate input"),
|
.key = ui.keyFromString("Sample rate input"),
|
||||||
.storage = &self.sample_rate_input,
|
.storage = &self.sample_rate_input,
|
||||||
.placeholder = placeholder,
|
.placeholder = placeholder,
|
||||||
.initial = initial,
|
.initial = project.sample_rate,
|
||||||
.invalid = self.parsed_sample_rate != project.sample_rate,
|
.invalid = self.parsed_sample_rate != project.sample_rate,
|
||||||
.editable = !self.app.isCollectionInProgress()
|
.editable = !self.app.isCollectionInProgress()
|
||||||
});
|
});
|
||||||
@ -379,28 +381,133 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
|
|||||||
|
|
||||||
_ = ui.label("Samples: {d}", .{ sample_count });
|
_ = ui.label("Samples: {d}", .{ sample_count });
|
||||||
|
|
||||||
var duration_str: []const u8 = "-";
|
var duration_str: ?[]const u8 = null;
|
||||||
if (sample_rate != null) {
|
if (sample_rate != null) {
|
||||||
const duration = @as(f64, @floatFromInt(sample_count)) / sample_rate.?;
|
const duration = @as(f64, @floatFromInt(sample_count)) / sample_rate.?;
|
||||||
if (utils.formatDuration(ui.frameAllocator(), duration)) |str| {
|
if (utils.formatDuration(ui.frameAllocator(), duration)) |str| {
|
||||||
duration_str = str;
|
duration_str = str;
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
}
|
}
|
||||||
_ = ui.label("Duration: {s}", .{ duration_str });
|
if (duration_str == null) {
|
||||||
|
duration_str = std.fmt.allocPrint(ui.frameAllocator(), "{d}", .{ sample_count }) catch null;
|
||||||
|
}
|
||||||
|
|
||||||
const new_sliding_window = try ui.numberInput(f64, .{
|
_ = ui.label("Duration: {s}", .{ duration_str orelse "-" });
|
||||||
.key = ui.keyFromString("Sliding window"),
|
|
||||||
.storage = &self.sliding_window_input
|
|
||||||
});
|
|
||||||
|
|
||||||
if (new_sliding_window != view.sliding_window) {
|
var deferred_remove: std.BoundedArray(usize, App.View.max_transforms) = .{};
|
||||||
if (new_sliding_window == null or new_sliding_window.? > 0) {
|
|
||||||
self.app.pushCommand(.{
|
for (0.., view.transforms.slice()) |i, *_transform| {
|
||||||
.update_sliding_window = .{
|
const transform: *App.Transform = _transform;
|
||||||
.view_id = view_id,
|
|
||||||
.sliding_window = new_sliding_window
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
}});
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
_ = 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -434,6 +541,8 @@ fn showMarkedRange(self: *MainScreen, view_id: Id, index: usize) void {
|
|||||||
_ = ui.label("Size: {d:.2}", .{ marked_range.range.size() });
|
_ = ui.label("Size: {d:.2}", .{ marked_range.range.size() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = ui.label("Samples: {d:.2}", .{ marked_range.range.size() });
|
||||||
|
|
||||||
if (marked_range.axis == .X) {
|
if (marked_range.axis == .X) {
|
||||||
if (marked_range.min) |min| {
|
if (marked_range.min) |min| {
|
||||||
_ = ui.label("Minimum: {d:.3}", .{ min });
|
_ = ui.label("Minimum: {d:.3}", .{ min });
|
||||||
|
391
src/ui.zig
391
src/ui.zig
@ -480,6 +480,7 @@ pub const Box = struct {
|
|||||||
sroll_offset: f32 = 0,
|
sroll_offset: f32 = 0,
|
||||||
hot: f32 = 0,
|
hot: f32 = 0,
|
||||||
active: f32 = 0,
|
active: f32 = 0,
|
||||||
|
open: bool = false
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Flag = enum {
|
pub const Flag = enum {
|
||||||
@ -538,6 +539,7 @@ pub const Box = struct {
|
|||||||
tooltip: ?[]const u8 = null,
|
tooltip: ?[]const u8 = null,
|
||||||
float_x: ?f32 = null,
|
float_x: ?f32 = null,
|
||||||
float_y: ?f32 = null,
|
float_y: ?f32 = null,
|
||||||
|
draw_on_top: bool = false,
|
||||||
|
|
||||||
// Variables that you probably shouldn't be touching
|
// Variables that you probably shouldn't be touching
|
||||||
last_used_frame: u64 = 0,
|
last_used_frame: u64 = 0,
|
||||||
@ -576,6 +578,14 @@ pub const Box = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iterChildrenDeep(self: *const Box) BoxChildDeepIterator {
|
||||||
|
return BoxChildDeepIterator{
|
||||||
|
.root = self.tree.index,
|
||||||
|
.boxes = self.ui.boxes.slice(),
|
||||||
|
.current_child = self.tree.first_child_index
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn iterParents(self: *const Box) BoxParentIterator {
|
pub fn iterParents(self: *const Box) BoxParentIterator {
|
||||||
return BoxParentIterator{
|
return BoxParentIterator{
|
||||||
.boxes = self.ui.boxes.slice(),
|
.boxes = self.ui.boxes.slice(),
|
||||||
@ -610,6 +620,15 @@ pub const Box = struct {
|
|||||||
self.text_lines.len = 0;
|
self.text_lines.len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn appendText(self: *Box, text: []const u8) void {
|
||||||
|
if (self.text) |self_text| {
|
||||||
|
self.text = std.mem.concat(self.allocator, u8, &.{ self_text, text }) catch return;
|
||||||
|
self.text_lines.len = 0;
|
||||||
|
} else {
|
||||||
|
self.setText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setFloatX(self: *Box, x: f32) void {
|
pub fn setFloatX(self: *Box, x: f32) void {
|
||||||
self.float_x = x;
|
self.float_x = x;
|
||||||
}
|
}
|
||||||
@ -618,6 +637,11 @@ pub const Box = struct {
|
|||||||
self.float_y = y;
|
self.float_y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setFloatPosition(self: *Box, x: f32, y: f32) void {
|
||||||
|
self.setFloatX(x);
|
||||||
|
self.setFloatY(y);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setFloatRect(self: *Box, float_rect: Rect) void {
|
pub fn setFloatRect(self: *Box, float_rect: Rect) void {
|
||||||
self.setFloatX(float_rect.x);
|
self.setFloatX(float_rect.x);
|
||||||
self.setFloatY(float_rect.y);
|
self.setFloatY(float_rect.y);
|
||||||
@ -760,7 +784,8 @@ pub const BoxOptions = struct {
|
|||||||
texture_color: ?rl.Color = null,
|
texture_color: ?rl.Color = null,
|
||||||
draw: ?Box.Draw = null,
|
draw: ?Box.Draw = null,
|
||||||
visual_hot: ?bool = null,
|
visual_hot: ?bool = null,
|
||||||
visual_active: ?bool = null
|
visual_active: ?bool = null,
|
||||||
|
draw_on_top: ?bool = null
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const root_box_key = Key.initString(0, "$root$");
|
pub const root_box_key = Key.initString(0, "$root$");
|
||||||
@ -781,6 +806,33 @@ const BoxChildIterator = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BoxChildDeepIterator = struct {
|
||||||
|
root: BoxIndex,
|
||||||
|
current_child: ?BoxIndex,
|
||||||
|
boxes: []Box,
|
||||||
|
|
||||||
|
pub fn next(self: *BoxChildDeepIterator) ?*Box {
|
||||||
|
const current_child = self.current_child orelse return null;
|
||||||
|
|
||||||
|
const box = &self.boxes[current_child];
|
||||||
|
|
||||||
|
var next_box: ?BoxIndex = null;
|
||||||
|
if (box.tree.first_child_index) |first_child_index| {
|
||||||
|
next_box = first_child_index;
|
||||||
|
} else if (box.tree.next_sibling_index) |next_sibling_index| {
|
||||||
|
next_box = next_sibling_index;
|
||||||
|
} else if (box.tree.parent_index) |parent_index| {
|
||||||
|
if (parent_index != self.root) {
|
||||||
|
const parent = &self.boxes[parent_index];
|
||||||
|
next_box = parent.tree.next_sibling_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_child = next_box;
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const BoxParentIterator = struct {
|
const BoxParentIterator = struct {
|
||||||
current_parent: ?BoxIndex,
|
current_parent: ?BoxIndex,
|
||||||
boxes: []Box,
|
boxes: []Box,
|
||||||
@ -1497,6 +1549,12 @@ fn layoutFloatingPositions(self: *UI, box: *Box, axis: Axis) void {
|
|||||||
const axis_position_target = vec2ByAxis(&target.persistent.position, axis);
|
const axis_position_target = vec2ByAxis(&target.persistent.position, axis);
|
||||||
|
|
||||||
axis_position.* += axis_position_target.*;
|
axis_position.* += axis_position_target.*;
|
||||||
|
|
||||||
|
var child_iter = box.iterChildrenDeep();
|
||||||
|
while (child_iter.next()) |child| {
|
||||||
|
const child_axis_position = vec2ByAxis(&child.persistent.position, axis);
|
||||||
|
child_axis_position.* += axis_position_target.*;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var child_iter = box.iterChildren();
|
var child_iter = box.iterChildren();
|
||||||
@ -1589,6 +1647,7 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
|||||||
.draw = opts.draw,
|
.draw = opts.draw,
|
||||||
.visual_hot = opts.visual_hot orelse false,
|
.visual_hot = opts.visual_hot orelse false,
|
||||||
.visual_active = opts.visual_active orelse false,
|
.visual_active = opts.visual_active orelse false,
|
||||||
|
.draw_on_top = opts.draw_on_top orelse false,
|
||||||
|
|
||||||
.last_used_frame = self.frame_index,
|
.last_used_frame = self.frame_index,
|
||||||
.key = key,
|
.key = key,
|
||||||
@ -1671,186 +1730,195 @@ pub fn draw(self: *UI) void {
|
|||||||
const root_box = self.getBoxByKey(root_box_key).?;
|
const root_box = self.getBoxByKey(root_box_key).?;
|
||||||
const mouse_tooltip = self.getBoxByKey(mouse_tooltip_box_key).?;
|
const mouse_tooltip = self.getBoxByKey(mouse_tooltip_box_key).?;
|
||||||
|
|
||||||
self.drawBox(root_box);
|
self.drawBox(root_box, false);
|
||||||
|
self.drawBox(root_box, true);
|
||||||
|
|
||||||
if (mouse_tooltip.hasChildren()) {
|
if (mouse_tooltip.hasChildren()) {
|
||||||
self.drawBox(mouse_tooltip);
|
self.drawBox(mouse_tooltip, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawBox(self: *UI, box: *Box) void {
|
fn drawBox(self: *UI, box: *Box, on_top_pass: ?bool) void {
|
||||||
const box_rect = box.rect();
|
var child_on_top_pass = on_top_pass;
|
||||||
|
|
||||||
const do_scissor = box.flags.contains(.clip_view);
|
if (on_top_pass == null or box.draw_on_top == on_top_pass) {
|
||||||
if (do_scissor) self.beginScissor(box_rect);
|
const box_rect = box.rect();
|
||||||
defer if (do_scissor) self.endScissor();
|
|
||||||
|
|
||||||
var value_shift: f32 = 0;
|
const do_scissor = box.flags.contains(.clip_view);
|
||||||
if (box.flags.contains(.draw_active) and box.persistent.active > 0.01) {
|
if (do_scissor) self.beginScissor(box_rect);
|
||||||
value_shift = -0.5 * box.persistent.active;
|
defer if (do_scissor) self.endScissor();
|
||||||
} else if (box.flags.contains(.draw_hot) and box.persistent.hot > 0.01) {
|
|
||||||
value_shift = 0.6 * box.persistent.hot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (box.background) |bg| {
|
var value_shift: f32 = 0;
|
||||||
rl.drawRectangleRec(box_rect, utils.shiftColorInHSV(bg, value_shift));
|
if (box.flags.contains(.draw_active) and box.persistent.active > 0.01) {
|
||||||
}
|
value_shift = -0.5 * box.persistent.active;
|
||||||
|
} else if (box.flags.contains(.draw_hot) and box.persistent.hot > 0.01) {
|
||||||
if (box.texture) |texture| {
|
value_shift = 0.6 * box.persistent.hot;
|
||||||
const source = rl.Rectangle{
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = @floatFromInt(texture.width),
|
|
||||||
.height = @floatFromInt(texture.height)
|
|
||||||
};
|
|
||||||
var destination = box_rect;
|
|
||||||
if (box.texture_size) |texture_size| {
|
|
||||||
destination = rect_utils.initCentered(destination, texture_size.x, texture_size.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.drawTexturePro(
|
if (box.background) |bg| {
|
||||||
texture,
|
rl.drawRectangleRec(box_rect, utils.shiftColorInHSV(bg, value_shift));
|
||||||
source,
|
}
|
||||||
destination,
|
|
||||||
rl.Vector2.zero(),
|
|
||||||
0,
|
|
||||||
box.texture_color orelse rl.Color.white
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const borders_with_coords = .{
|
if (box.texture) |texture| {
|
||||||
.{ box.borders.left , rl.Vector2.init(0, 0), rl.Vector2.init(0, 1), rl.Vector2.init( 1, 0) },
|
const source = rl.Rectangle{
|
||||||
.{ box.borders.right , rl.Vector2.init(1, 0), rl.Vector2.init(1, 1), rl.Vector2.init(-1, 0) },
|
.x = 0,
|
||||||
.{ box.borders.top , rl.Vector2.init(0, 0), rl.Vector2.init(1, 0), rl.Vector2.init( 0, 1) },
|
.y = 0,
|
||||||
.{ box.borders.bottom, rl.Vector2.init(0, 1), rl.Vector2.init(1, 1), rl.Vector2.init( 0, -1) }
|
.width = @floatFromInt(texture.width),
|
||||||
};
|
.height = @floatFromInt(texture.height)
|
||||||
inline for (borders_with_coords) |border_with_coords| {
|
};
|
||||||
const border = border_with_coords[0];
|
var destination = box_rect;
|
||||||
const line_from = border_with_coords[1];
|
if (box.texture_size) |texture_size| {
|
||||||
const line_to = border_with_coords[2];
|
destination = rect_utils.initCentered(destination, texture_size.x, texture_size.y);
|
||||||
const inset_direction: rl.Vector2 = border_with_coords[3];
|
}
|
||||||
|
|
||||||
if (border.size > 0) {
|
rl.drawTexturePro(
|
||||||
const inset = inset_direction.multiply(Vec2.init(border.size/2, border.size/2));
|
texture,
|
||||||
rl.drawLineEx(
|
source,
|
||||||
rect_utils.positionAt(box_rect, line_from).add(inset),
|
destination,
|
||||||
rect_utils.positionAt(box_rect, line_to).add(inset),
|
rl.Vector2.zero(),
|
||||||
border.size,
|
0,
|
||||||
utils.shiftColorInHSV(border.color, value_shift)
|
box.texture_color orelse rl.Color.white
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (box.draw) |box_draw| {
|
const borders_with_coords = .{
|
||||||
box_draw.do(box_draw.ctx, box);
|
.{ box.borders.left , rl.Vector2.init(0, 0), rl.Vector2.init(0, 1), rl.Vector2.init( 1, 0) },
|
||||||
}
|
.{ box.borders.right , rl.Vector2.init(1, 0), rl.Vector2.init(1, 1), rl.Vector2.init(-1, 0) },
|
||||||
|
.{ box.borders.top , rl.Vector2.init(0, 0), rl.Vector2.init(1, 0), rl.Vector2.init( 0, 1) },
|
||||||
|
.{ box.borders.bottom, rl.Vector2.init(0, 1), rl.Vector2.init(1, 1), rl.Vector2.init( 0, -1) }
|
||||||
|
};
|
||||||
|
inline for (borders_with_coords) |border_with_coords| {
|
||||||
|
const border = border_with_coords[0];
|
||||||
|
const line_from = border_with_coords[1];
|
||||||
|
const line_to = border_with_coords[2];
|
||||||
|
const inset_direction: rl.Vector2 = border_with_coords[3];
|
||||||
|
|
||||||
const alignment_x_coeff = box.alignment.x.getCoefficient();
|
if (border.size > 0) {
|
||||||
const alignment_y_coeff = box.alignment.y.getCoefficient();
|
const inset = inset_direction.multiply(Vec2.init(border.size/2, border.size/2));
|
||||||
|
rl.drawLineEx(
|
||||||
if (box.text) |text| {
|
rect_utils.positionAt(box_rect, line_from).add(inset),
|
||||||
const font_face = Assets.font(box.font);
|
rect_utils.positionAt(box_rect, line_to).add(inset),
|
||||||
var text_position = box.persistent.position;
|
border.size,
|
||||||
text_position.x += box.padding.left;
|
utils.shiftColorInHSV(border.color, value_shift)
|
||||||
text_position.y += box.padding.top;
|
|
||||||
|
|
||||||
const lines: [][]u8 = box.text_lines.slice();
|
|
||||||
|
|
||||||
const available_width = box.availableChildrenSize(.X);
|
|
||||||
const available_height = box.availableChildrenSize(.Y);
|
|
||||||
|
|
||||||
if (lines.len == 0) {
|
|
||||||
const text_size = font_face.measureText(text);
|
|
||||||
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
|
||||||
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
|
||||||
|
|
||||||
font_face.drawText(text, text_position, box.text_color);
|
|
||||||
} else {
|
|
||||||
// TODO: Don't call `measureTextLines`,
|
|
||||||
// Because in the end `measureText` will be called twice for each line
|
|
||||||
const text_size = font_face.measureTextLines(lines);
|
|
||||||
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
|
||||||
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
|
||||||
|
|
||||||
var offset_y: f32 = 0;
|
|
||||||
|
|
||||||
for (lines) |line| {
|
|
||||||
const line_size = font_face.measureText(line);
|
|
||||||
const offset_x = (text_size.x - line_size.x) * alignment_x_coeff;
|
|
||||||
|
|
||||||
font_face.drawText(
|
|
||||||
line,
|
|
||||||
text_position.add(.{ .x = offset_x, .y = offset_y }),
|
|
||||||
box.text_color
|
|
||||||
);
|
);
|
||||||
|
|
||||||
offset_y += font_face.getSize() * font_face.line_height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (box.scientific_number) |scientific_number| {
|
if (box.draw) |box_draw| {
|
||||||
const regular = Assets.font(box.font);
|
box_draw.do(box_draw.ctx, box);
|
||||||
const superscript = Assets.font(.{
|
}
|
||||||
.size = box.font.size * 0.8,
|
|
||||||
.variant = box.font.variant
|
|
||||||
});
|
|
||||||
|
|
||||||
var text_position = box.persistent.position;
|
const alignment_x_coeff = box.alignment.x.getCoefficient();
|
||||||
text_position.x += box.padding.left;
|
const alignment_y_coeff = box.alignment.y.getCoefficient();
|
||||||
text_position.y += box.padding.top;
|
|
||||||
|
|
||||||
const available_width = box.availableChildrenSize(.X);
|
if (box.text) |text| {
|
||||||
const available_height = box.availableChildrenSize(.Y);
|
const font_face = Assets.font(box.font);
|
||||||
|
var text_position = box.persistent.position;
|
||||||
|
text_position.x += box.padding.left;
|
||||||
|
text_position.y += box.padding.top;
|
||||||
|
|
||||||
const exponent = @floor(std.math.log10(scientific_number));
|
const lines: [][]u8 = box.text_lines.slice();
|
||||||
const multiplier = std.math.pow(f64, 10, exponent);
|
|
||||||
const coefficient = scientific_number / multiplier;
|
|
||||||
|
|
||||||
// const text = std.fmt.allocPrint(box.allocator, "{d:.2}x10{d}", .{coefficient, exponent}) catch "";
|
const available_width = box.availableChildrenSize(.X);
|
||||||
|
const available_height = box.availableChildrenSize(.Y);
|
||||||
|
|
||||||
var coefficient_buff: [256]u8 = undefined;
|
if (lines.len == 0) {
|
||||||
const coefficient_str = std.fmt.formatFloat(&coefficient_buff, coefficient, .{ .mode = .decimal, .precision = box.scientific_precision }) catch "";
|
const text_size = font_face.measureText(text);
|
||||||
const exponent_str = std.fmt.allocPrint(box.allocator, "{d:.0}", .{exponent}) catch "";
|
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
||||||
|
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
||||||
|
|
||||||
var text_size = regular.measureText(coefficient_str);
|
font_face.drawText(text, text_position, box.text_color);
|
||||||
text_size.x += regular.measureWidth("x10");
|
} else {
|
||||||
text_size.x += superscript.measureWidth(exponent_str);
|
// TODO: Don't call `measureTextLines`,
|
||||||
|
// Because in the end `measureText` will be called twice for each line
|
||||||
|
const text_size = font_face.measureTextLines(lines);
|
||||||
|
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
||||||
|
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
||||||
|
|
||||||
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
var offset_y: f32 = 0;
|
||||||
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
|
||||||
|
|
||||||
var ctx = FontFace.DrawTextContext{
|
for (lines) |line| {
|
||||||
.font_face = regular,
|
const line_size = font_face.measureText(line);
|
||||||
.origin = text_position,
|
const offset_x = (text_size.x - line_size.x) * alignment_x_coeff;
|
||||||
.tint = box.text_color
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.drawText(coefficient_str);
|
font_face.drawText(
|
||||||
ctx.advanceY(-0.04);
|
line,
|
||||||
ctx.advanceX(0.1);
|
text_position.add(.{ .x = offset_x, .y = offset_y }),
|
||||||
ctx.drawText("x");
|
box.text_color
|
||||||
ctx.advanceY(0.04);
|
);
|
||||||
ctx.drawText("10");
|
|
||||||
|
|
||||||
ctx.font_face = superscript;
|
offset_y += font_face.getSize() * font_face.line_height;
|
||||||
ctx.advanceY(-0.2);
|
}
|
||||||
ctx.drawText(exponent_str);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draw_debug) {
|
if (box.scientific_number) |scientific_number| {
|
||||||
if (self.isKeyActive(box.key)) {
|
const regular = Assets.font(box.font);
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
|
const superscript = Assets.font(.{
|
||||||
} else if (self.isKeyHot(box.key)) {
|
.size = box.font.size * 0.8,
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.orange);
|
.variant = box.font.variant
|
||||||
} else {
|
});
|
||||||
rl.drawRectangleLinesEx(box_rect, 1, rl.Color.magenta);
|
|
||||||
|
var text_position = box.persistent.position;
|
||||||
|
text_position.x += box.padding.left;
|
||||||
|
text_position.y += box.padding.top;
|
||||||
|
|
||||||
|
const available_width = box.availableChildrenSize(.X);
|
||||||
|
const available_height = box.availableChildrenSize(.Y);
|
||||||
|
|
||||||
|
const exponent = @floor(std.math.log10(scientific_number));
|
||||||
|
const multiplier = std.math.pow(f64, 10, exponent);
|
||||||
|
const coefficient = scientific_number / multiplier;
|
||||||
|
|
||||||
|
// const text = std.fmt.allocPrint(box.allocator, "{d:.2}x10{d}", .{coefficient, exponent}) catch "";
|
||||||
|
|
||||||
|
var coefficient_buff: [256]u8 = undefined;
|
||||||
|
const coefficient_str = std.fmt.formatFloat(&coefficient_buff, coefficient, .{ .mode = .decimal, .precision = box.scientific_precision }) catch "";
|
||||||
|
const exponent_str = std.fmt.allocPrint(box.allocator, "{d:.0}", .{exponent}) catch "";
|
||||||
|
|
||||||
|
var text_size = regular.measureText(coefficient_str);
|
||||||
|
text_size.x += regular.measureWidth("x10");
|
||||||
|
text_size.x += superscript.measureWidth(exponent_str);
|
||||||
|
|
||||||
|
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
||||||
|
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
||||||
|
|
||||||
|
var ctx = FontFace.DrawTextContext{
|
||||||
|
.font_face = regular,
|
||||||
|
.origin = text_position,
|
||||||
|
.tint = box.text_color
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.drawText(coefficient_str);
|
||||||
|
ctx.advanceY(-0.04);
|
||||||
|
ctx.advanceX(0.1);
|
||||||
|
ctx.drawText("x");
|
||||||
|
ctx.advanceY(0.04);
|
||||||
|
ctx.drawText("10");
|
||||||
|
|
||||||
|
ctx.font_face = superscript;
|
||||||
|
ctx.advanceY(-0.2);
|
||||||
|
ctx.drawText(exponent_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw_debug) {
|
||||||
|
if (self.isKeyActive(box.key)) {
|
||||||
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
|
||||||
|
} else if (self.isKeyHot(box.key)) {
|
||||||
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.orange);
|
||||||
|
} else {
|
||||||
|
rl.drawRectangleLinesEx(box_rect, 1, rl.Color.magenta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.draw_on_top) {
|
||||||
|
child_on_top_pass = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var child_iter = box.iterChildren();
|
var child_iter = box.iterChildren();
|
||||||
while (child_iter.next()) |child| {
|
while (child_iter.next()) |child| {
|
||||||
self.drawBox(child);
|
self.drawBox(child, child_on_top_pass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2269,9 +2337,11 @@ pub const TextInputOptions = struct {
|
|||||||
key: Key,
|
key: Key,
|
||||||
storage: *TextInputStorage,
|
storage: *TextInputStorage,
|
||||||
editable: bool = true,
|
editable: bool = true,
|
||||||
|
width: f32 = 200,
|
||||||
|
|
||||||
initial: ?[]const u8 = null,
|
initial: ?[]const u8 = null,
|
||||||
placeholder: ?[]const u8 = null,
|
placeholder: ?[]const u8 = null,
|
||||||
|
postfix: ?[]const u8 = null,
|
||||||
text_color: rl.Color = srcery.black
|
text_color: rl.Color = srcery.black
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2280,9 +2350,13 @@ pub const NumberInputOptions = struct {
|
|||||||
storage: *TextInputStorage,
|
storage: *TextInputStorage,
|
||||||
invalid: bool = false,
|
invalid: bool = false,
|
||||||
editable: bool = true,
|
editable: bool = true,
|
||||||
|
width: f32 = 200,
|
||||||
|
|
||||||
initial: ?[]const u8 = null,
|
display_scalar: ?f64 = null,
|
||||||
|
|
||||||
|
initial: ?f64 = null,
|
||||||
placeholder: ?[]const u8 = null,
|
placeholder: ?[]const u8 = null,
|
||||||
|
postfix: ?[]const u8 = null,
|
||||||
text_color: rl.Color = srcery.black,
|
text_color: rl.Color = srcery.black,
|
||||||
invalid_color: rl.Color = srcery.red
|
invalid_color: rl.Color = srcery.red
|
||||||
};
|
};
|
||||||
@ -2429,7 +2503,7 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
|||||||
|
|
||||||
const container = self.createBox(.{
|
const container = self.createBox(.{
|
||||||
.key = opts.key,
|
.key = opts.key,
|
||||||
.size_x = Sizing.initGrowUpTo(.{ .pixels = 200 }),
|
.size_x = Sizing.initGrowUpTo(.{ .pixels = opts.width }),
|
||||||
.size_y = Sizing.initFixed(Unit.initPixels(self.rem(1))),
|
.size_y = Sizing.initFixed(Unit.initPixels(self.rem(1))),
|
||||||
.flags = &.{ .clickable, .clip_view, .draggable },
|
.flags = &.{ .clickable, .clip_view, .draggable },
|
||||||
.background = srcery.bright_white,
|
.background = srcery.bright_white,
|
||||||
@ -2485,7 +2559,7 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
|||||||
storage.shown_slice_end = cursor_stop_x + shown_window_size;
|
storage.shown_slice_end = cursor_stop_x + shown_window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = self.createBox(.{
|
const shown_text = self.createBox(.{
|
||||||
.text_color = text_color,
|
.text_color = text_color,
|
||||||
.text = text,
|
.text = text,
|
||||||
.float_relative_to = container,
|
.float_relative_to = container,
|
||||||
@ -2498,6 +2572,9 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
|||||||
.align_y = .center,
|
.align_y = .center,
|
||||||
.align_x = .start
|
.align_x = .start
|
||||||
});
|
});
|
||||||
|
if (opts.postfix) |postfix| {
|
||||||
|
shown_text.appendText(postfix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const container_signal = self.signal(container);
|
const container_signal = self.signal(container);
|
||||||
@ -2730,12 +2807,15 @@ pub fn numberInput(self: *UI, T: type, opts: NumberInputOptions) !?T {
|
|||||||
var text_opts = TextInputOptions{
|
var text_opts = TextInputOptions{
|
||||||
.key = opts.key,
|
.key = opts.key,
|
||||||
.storage = opts.storage,
|
.storage = opts.storage,
|
||||||
.initial = opts.initial,
|
|
||||||
.text_color = opts.text_color,
|
.text_color = opts.text_color,
|
||||||
.placeholder = opts.placeholder,
|
.placeholder = opts.placeholder,
|
||||||
.editable = opts.editable
|
.editable = opts.editable,
|
||||||
|
.postfix = opts.postfix,
|
||||||
|
.width = opts.width
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const display_scalar = opts.display_scalar orelse 1;
|
||||||
|
|
||||||
var is_invalid = opts.invalid;
|
var is_invalid = opts.invalid;
|
||||||
if (storage.buffer.items.len > 0 and std.meta.isError(std.fmt.parseFloat(T, storage.buffer.items))) {
|
if (storage.buffer.items.len > 0 and std.meta.isError(std.fmt.parseFloat(T, storage.buffer.items))) {
|
||||||
is_invalid = true;
|
is_invalid = true;
|
||||||
@ -2747,8 +2827,17 @@ pub fn numberInput(self: *UI, T: type, opts: NumberInputOptions) !?T {
|
|||||||
|
|
||||||
try self.textInput(text_opts);
|
try self.textInput(text_opts);
|
||||||
|
|
||||||
|
const box = self.getBoxByKey(opts.key).?;
|
||||||
|
if (opts.initial != null and box.created) {
|
||||||
|
opts.storage.buffer.clearAndFree();
|
||||||
|
|
||||||
|
const initial = opts.initial.? / display_scalar;
|
||||||
|
const initial_text = try std.fmt.allocPrint(box.allocator, "{d}", .{ initial });
|
||||||
|
try opts.storage.buffer.appendSlice(initial_text);
|
||||||
|
}
|
||||||
|
|
||||||
if (std.fmt.parseFloat(T, storage.buffer.items)) |new_value| {
|
if (std.fmt.parseFloat(T, storage.buffer.items)) |new_value| {
|
||||||
return new_value;
|
return new_value * display_scalar;
|
||||||
} else |_| {
|
} else |_| {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user