add custom draw callback to UI
This commit is contained in:
parent
23c4b99455
commit
ae26d2e1ba
50
build.zig
50
build.zig
@ -98,6 +98,12 @@ pub fn build(b: *std.Build) !void {
|
||||
const stb_image_lib = buildStbImage(b);
|
||||
const cute_aseprite_lib = buildCuteAseprite(b, raylib_dep);
|
||||
|
||||
const generate_tool = b.addExecutable(.{
|
||||
.name = "generate",
|
||||
.root_source_file = b.path("tools/generate.zig"),
|
||||
.target = target,
|
||||
});
|
||||
|
||||
const png_to_icon_tool = b.addExecutable(.{
|
||||
.name = "png-to-icon",
|
||||
.root_source_file = b.path("tools/png-to-icon.zig"),
|
||||
@ -142,22 +148,38 @@ pub fn build(b: *std.Build) !void {
|
||||
}
|
||||
|
||||
b.installArtifact(exe);
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
|
||||
{ // Run genration tool
|
||||
|
||||
const run_cmd = b.addRunArtifact(generate_tool);
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("generate", "Run sample generation tool");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the program");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
{ // Run main program
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const unit_tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
const run_step = b.step("run", "Run the program");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
const run_unit_tests = b.addRunArtifact(unit_tests);
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_unit_tests.step);
|
||||
{ // Unit tests
|
||||
const unit_tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const run_unit_tests = b.addRunArtifact(unit_tests);
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_unit_tests.step);
|
||||
}
|
||||
}
|
||||
|
@ -751,7 +751,7 @@ pub fn tick(self: *App) !void {
|
||||
if (view.reference != .channel) continue;
|
||||
if (!view.follow) continue;
|
||||
|
||||
const sample_rate = self.project.sample_rate orelse 1;
|
||||
const sample_rate = self.project.getSampleRate() orelse 1;
|
||||
|
||||
view.graph_opts.y_range = view.available_y_range;
|
||||
view.graph_opts.x_range.lower = 0;
|
||||
|
27
src/main.zig
27
src/main.zig
@ -103,15 +103,35 @@ pub fn main() !void {
|
||||
|
||||
if (builtin.mode == .Debug) {
|
||||
_ = try app.addView(.{
|
||||
.channel = try app.addChannel("Dev1/ai0")
|
||||
.file = try app.addFile("./samples-5k.bin")
|
||||
});
|
||||
|
||||
_ = try app.addView(.{
|
||||
.channel = try app.addChannel("Dev3/ao0")
|
||||
.file = try app.addFile("./samples-50k.bin")
|
||||
});
|
||||
|
||||
_ = try app.addView(.{
|
||||
.file = try app.addFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin")
|
||||
.file = try app.addFile("./samples-300k.bin")
|
||||
});
|
||||
|
||||
_ = try app.addView(.{
|
||||
.file = try app.addFile("./samples-9m.bin")
|
||||
});
|
||||
|
||||
_ = try app.addView(.{
|
||||
.file = try app.addFile("./samples-18m.bin")
|
||||
});
|
||||
|
||||
// _ = try app.addView(.{
|
||||
// .channel = try app.addChannel("Dev1/ai0")
|
||||
// });
|
||||
// _ = try app.addView(.{
|
||||
// .channel = try app.addChannel("Dev3/ao0")
|
||||
// });
|
||||
// _ = try app.addView(.{
|
||||
// .file = try app.addFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin")
|
||||
// });
|
||||
|
||||
var cwd_realpath_buff: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const cwd_realpath = try std.fs.cwd().realpath(".", &cwd_realpath_buff);
|
||||
|
||||
@ -119,6 +139,7 @@ pub fn main() !void {
|
||||
errdefer allocator.free(allocator);
|
||||
|
||||
app.project.save_location = save_location;
|
||||
app.project.sample_rate = 5000;
|
||||
|
||||
// try app.appendChannelFromDevice("Dev1/ai0");
|
||||
// try app.appendChannelFromDevice("Dev3/ao0");
|
||||
|
@ -52,6 +52,7 @@ protocol_error_message: ?[]const u8 = null,
|
||||
protocol_graph_cache: Graph.Cache = .{},
|
||||
preview_samples: std.ArrayListUnmanaged(f64) = .{},
|
||||
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
|
||||
view_settings: ?Id = null,
|
||||
|
||||
pub fn init(app: *App) !MainScreen {
|
||||
const allocator = app.allocator;
|
||||
@ -223,203 +224,129 @@ fn showChannelViewGraph(self: *MainScreen, view_id: Id) *UI.Box {
|
||||
return graph_box;
|
||||
}
|
||||
|
||||
fn getLineOnRuler(
|
||||
self: *MainScreen,
|
||||
view_id: Id,
|
||||
ruler: *UI.Box,
|
||||
const RulerContext = struct {
|
||||
one_unit: ?f64,
|
||||
render_range: RangeF64,
|
||||
available_range: RangeF64,
|
||||
axis: UI.Axis,
|
||||
rect: rl.Rectangle = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
|
||||
|
||||
along_axis_pos: f64,
|
||||
cross_axis_pos: f64,
|
||||
cross_axis_size: f64
|
||||
) rl.Rectangle {
|
||||
const view = self.app.getView(view_id).?;
|
||||
const shown_size = view.getGraphView(axis).size();
|
||||
fn init(axis: UI.Axis, project: *App.Project, view_id: Id) RulerContext {
|
||||
const view = project.views.get(view_id).?;
|
||||
|
||||
const along_axis_size = shown_size / switch (axis) {
|
||||
.X => ruler.persistent.size.x,
|
||||
.Y => ruler.persistent.size.y,
|
||||
};
|
||||
|
||||
return self.getRectOnRuler(
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
|
||||
along_axis_pos,
|
||||
along_axis_size,
|
||||
cross_axis_pos,
|
||||
cross_axis_size
|
||||
);
|
||||
}
|
||||
|
||||
fn getRectOnRuler(
|
||||
self: *MainScreen,
|
||||
view_id: Id,
|
||||
ruler: *UI.Box,
|
||||
axis: UI.Axis,
|
||||
|
||||
along_axis_pos: f64,
|
||||
along_axis_size: f64,
|
||||
cross_axis_pos: f64,
|
||||
cross_axis_size: f64
|
||||
) rl.Rectangle {
|
||||
assert(0 <= cross_axis_size and cross_axis_size <= 1);
|
||||
|
||||
const rect = ruler.rect();
|
||||
const rect_height: f64 = @floatCast(rect.height);
|
||||
const rect_width: f64 = @floatCast(rect.width);
|
||||
|
||||
const view = self.app.getView(view_id).?;
|
||||
const view_range = view.getGraphView(axis).*;
|
||||
|
||||
if (axis == .X) {
|
||||
const width_range = RangeF64.init(0, rect.width);
|
||||
var result = rl.Rectangle{
|
||||
.width = @floatCast(along_axis_size / view_range.size() * rect_width),
|
||||
.height = @floatCast(rect_height * cross_axis_size),
|
||||
.x = @floatCast(view_range.remapTo(width_range, along_axis_pos)),
|
||||
.y = @floatCast(rect_height * cross_axis_pos),
|
||||
return RulerContext{
|
||||
.one_unit = switch (axis) {
|
||||
.X => project.getSampleRate(),
|
||||
.Y => 1
|
||||
},
|
||||
.render_range = view.getGraphView(axis).*,
|
||||
.available_range = view.getAvailableView(axis),
|
||||
.axis = axis,
|
||||
};
|
||||
|
||||
if (result.width < 0) {
|
||||
result.x += result.width;
|
||||
result.width *= -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
const height_range = RangeF64.init(0, rect.height);
|
||||
var result = rl.Rectangle{
|
||||
.width = @floatCast(rect_width * cross_axis_size),
|
||||
.height = @floatCast(along_axis_size / view_range.size() * rect_height),
|
||||
.x = @floatCast(rect_width * (1 - cross_axis_pos - cross_axis_size)),
|
||||
.y = @floatCast(view_range.remapTo(height_range, along_axis_pos + along_axis_size)),
|
||||
};
|
||||
|
||||
if (result.height < 0) {
|
||||
result.y += result.height;
|
||||
result.height *= -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
fn showRulerTicksRange(
|
||||
self: *MainScreen,
|
||||
view_id: Id,
|
||||
ruler: *UI.Box,
|
||||
axis: UI.Axis,
|
||||
|
||||
from: f64,
|
||||
to: f64,
|
||||
step: f64,
|
||||
|
||||
marker_size: f64
|
||||
) void {
|
||||
var marker = from;
|
||||
while (marker < to) : (marker += step) {
|
||||
_ = self.app.ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, marker, 0, marker_size),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn showRulerTicks(self: *MainScreen, view_id: Id, axis: UI.Axis) void {
|
||||
const view = self.app.getView(view_id).?;
|
||||
|
||||
const view_range = view.getGraphView(axis);
|
||||
const full_range = view.getAvailableView(axis);
|
||||
|
||||
var ui = &self.app.ui;
|
||||
const ruler = ui.parentBox().?;
|
||||
|
||||
const ruler_rect = ruler.rect();
|
||||
const ruler_rect_size_along_axis = switch (axis) {
|
||||
.X => ruler_rect.width,
|
||||
.Y => ruler_rect.height
|
||||
};
|
||||
|
||||
if (ruler_rect_size_along_axis == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view_range.size() == 0) {
|
||||
return;
|
||||
}
|
||||
fn getPoint(self: *RulerContext, along_axis_pos: f64, cross_axis_pos: f64) rl.Vector2 {
|
||||
const rect_width: f64 = @floatCast(self.rect.width);
|
||||
const rect_height: f64 = @floatCast(self.rect.height);
|
||||
|
||||
if (full_range.size() == 0) {
|
||||
return;
|
||||
}
|
||||
var x: f64 = undefined;
|
||||
var y: f64 = undefined;
|
||||
|
||||
const ideal_pixels_per_division = 150;
|
||||
var subdivisions: f32 = 20;
|
||||
subdivisions = 20;
|
||||
while (true) {
|
||||
assert(subdivisions > 0);
|
||||
const step = full_range.size() / subdivisions;
|
||||
const pixels_per_division = step / view_range.size() * ruler_rect_size_along_axis;
|
||||
assert(pixels_per_division > 0);
|
||||
|
||||
if (pixels_per_division > ideal_pixels_per_division*2) {
|
||||
subdivisions *= 2;
|
||||
} else if (pixels_per_division < ideal_pixels_per_division/2) {
|
||||
subdivisions /= 2;
|
||||
if (self.axis == .X) {
|
||||
x = remap(f64, self.render_range.lower, self.render_range.upper, 0, rect_width, along_axis_pos);
|
||||
y = cross_axis_pos * rect_height;
|
||||
} else {
|
||||
break;
|
||||
assert(self.axis == .Y);
|
||||
|
||||
x = (1 - cross_axis_pos) * rect_width;
|
||||
y = remap(f64, self.render_range.lower, self.render_range.upper, 0, rect_height, along_axis_pos);
|
||||
}
|
||||
|
||||
return rl.Vector2{
|
||||
.x = self.rect.x + @as(f32, @floatCast(x)),
|
||||
.y = self.rect.y + @as(f32, @floatCast(y)),
|
||||
};
|
||||
}
|
||||
|
||||
fn getLine(self: *RulerContext, along_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
||||
const along_axis_size = self.render_range.size() / switch(self.axis) {
|
||||
.X => @as(f64, @floatCast(self.rect.width)),
|
||||
.Y => @as(f64, @floatCast(self.rect.height))
|
||||
};
|
||||
|
||||
return self.getRect(
|
||||
along_axis_pos,
|
||||
along_axis_size,
|
||||
0,
|
||||
cross_axis_size
|
||||
);
|
||||
}
|
||||
|
||||
fn getRect(self: *RulerContext, along_axis_pos: f64, along_axis_size: f64, cross_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
||||
const pos = self.getPoint(along_axis_pos, cross_axis_pos);
|
||||
const corner = self.getPoint(along_axis_pos + along_axis_size, cross_axis_pos + cross_axis_size);
|
||||
var rect = rl.Rectangle{
|
||||
.x = pos.x,
|
||||
.y = pos.y,
|
||||
.width = corner.x - pos.x,
|
||||
.height = corner.y - pos.y
|
||||
};
|
||||
|
||||
if (rect.width < 0) {
|
||||
rect.x += rect.width;
|
||||
rect.width *= -1;
|
||||
}
|
||||
|
||||
if (rect.height < 0) {
|
||||
rect.y += rect.height;
|
||||
rect.height *= -1;
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
fn drawLine(self: *RulerContext, along_axis_pos: f64, cross_axis_size: f64, color: rl.Color) void {
|
||||
rl.drawLineV(
|
||||
self.getPoint(along_axis_pos, 0),
|
||||
self.getPoint(along_axis_pos, cross_axis_size),
|
||||
color
|
||||
);
|
||||
}
|
||||
|
||||
fn drawTicks(self: *RulerContext, from: f64, to: f64, step: f64, line_size: f64, color: rl.Color) void {
|
||||
var position = from;
|
||||
while (position < to) : (position += step) {
|
||||
self.drawLine(position, line_size, color);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const step = full_range.size() / subdivisions;
|
||||
fn drawRulerTicks(_ctx: ?*anyopaque, box: *UI.Box) void {
|
||||
const ctx: *RulerContext = @ptrCast(@alignCast(_ctx));
|
||||
ctx.rect = box.rect();
|
||||
|
||||
{
|
||||
_ = self.app.ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, full_range.lower, 0, 0.75),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
ctx.drawLine(ctx.available_range.lower, 1, srcery.yellow);
|
||||
ctx.drawLine(ctx.available_range.upper, 1, srcery.yellow);
|
||||
|
||||
if (ctx.available_range.hasExclusive(0)) {
|
||||
ctx.drawLine(0, 0.75, srcery.yellow);
|
||||
}
|
||||
|
||||
{
|
||||
_ = self.app.ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, full_range.upper, 0, 0.75),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
var one_unit = ctx.one_unit orelse return;
|
||||
|
||||
while (ctx.render_range.size() < 5*one_unit) {
|
||||
one_unit /= 2;
|
||||
}
|
||||
|
||||
if (full_range.hasExclusive(0)) {
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, 0, 0, 0.75),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
while (ctx.render_range.size() > 50*one_unit) {
|
||||
one_unit *= 2;
|
||||
}
|
||||
|
||||
const ticks_range = view_range.grow(step).intersectPositive(full_range);
|
||||
var ticks_range = ctx.render_range.grow(one_unit).intersectPositive(ctx.available_range);
|
||||
ticks_range.lower = utils.roundNearestTowardZero(f64, ticks_range.lower, one_unit);
|
||||
|
||||
self.showRulerTicksRange(
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
utils.roundNearestTowardZero(f64, ticks_range.lower, step) + step/2,
|
||||
ticks_range.upper,
|
||||
step,
|
||||
0.5
|
||||
);
|
||||
|
||||
self.showRulerTicksRange(
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
utils.roundNearestTowardZero(f64, ticks_range.lower, step),
|
||||
ticks_range.upper,
|
||||
step,
|
||||
0.25
|
||||
);
|
||||
ctx.drawTicks(ticks_range.lower, ticks_range.upper, one_unit, 0.5, srcery.yellow);
|
||||
ctx.drawTicks(ticks_range.lower + one_unit/2, ticks_range.upper, one_unit, 0.25, srcery.yellow);
|
||||
}
|
||||
|
||||
fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
||||
@ -428,7 +355,7 @@ fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
||||
var ruler = ui.createBox(.{
|
||||
.key = key,
|
||||
.background = srcery.hard_black,
|
||||
.flags = &.{ .clip_view, .clickable, .scrollable },
|
||||
.flags = &.{ .clickable, .scrollable, .clip_view },
|
||||
.hot_cursor = .mouse_cursor_pointing_hand
|
||||
});
|
||||
if (axis == .X) {
|
||||
@ -442,15 +369,42 @@ fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
||||
return ruler;
|
||||
}
|
||||
|
||||
fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: UI.Axis) void {
|
||||
fn formatDuration(allocator: std.mem.Allocator, total_seconds: f64) ![]u8 {
|
||||
const seconds = @mod(total_seconds, @as(f64, @floatFromInt(std.time.s_per_min)));
|
||||
const minutes = total_seconds / std.time.s_per_min;
|
||||
return try std.fmt.allocPrint(allocator, "{d:.0}m {d:.3}s", .{ minutes, seconds });
|
||||
}
|
||||
|
||||
fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: UI.Axis) !void {
|
||||
var ui = &self.app.ui;
|
||||
|
||||
const view = self.app.getView(view_id) orelse return;
|
||||
|
||||
var ruler_ctx = RulerContext.init(axis, &self.app.project, view_id);
|
||||
var graph_ctx = ruler_ctx;
|
||||
ruler_ctx.rect = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = ruler.persistent.size.x,
|
||||
.height = ruler.persistent.size.y
|
||||
};
|
||||
graph_ctx.rect = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = graph_box.persistent.size.x,
|
||||
.height = graph_box.persistent.size.y
|
||||
};
|
||||
|
||||
ruler.beginChildren();
|
||||
defer ruler.endChildren();
|
||||
|
||||
self.showRulerTicks(view_id, axis);
|
||||
const ctx = try ui.frameAllocator().create(RulerContext);
|
||||
ctx.* = RulerContext.init(axis, &self.app.project, view_id);
|
||||
|
||||
ruler.draw = .{
|
||||
.ctx = ctx,
|
||||
.do = drawRulerTicks
|
||||
};
|
||||
|
||||
const signal = ui.signal(ruler);
|
||||
const mouse_position = switch (axis) {
|
||||
@ -480,12 +434,16 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
||||
const project = &self.app.project;
|
||||
|
||||
if (view.getAvailableView(axis).hasInclusive(mouse_position_on_graph)) {
|
||||
const sample_rate = project.getSampleRate();
|
||||
|
||||
if (axis == .Y and view.unit != null) {
|
||||
const unit_name = view.unit.?.name() orelse "Unknown";
|
||||
_ = ui.label("{s}: {d:.3}", .{unit_name, mouse_position_on_graph});
|
||||
} else if (axis == .X and project.sample_rate != null) {
|
||||
const sample_rate = project.sample_rate.?;
|
||||
_ = ui.label("{d:.3}s", .{mouse_position_on_graph / sample_rate});
|
||||
} else if (axis == .X and sample_rate != null) {
|
||||
const seconds = mouse_position_on_graph / sample_rate.?;
|
||||
const frame_allocator = ui.frameAllocator();
|
||||
_ = ui.label("{s}", .{ formatDuration(frame_allocator, seconds) catch "-" });
|
||||
|
||||
} else {
|
||||
_ = ui.label("{d:.3}", .{mouse_position_on_graph});
|
||||
}
|
||||
@ -510,13 +468,13 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
||||
if (zoom_start != null) {
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, zoom_start.?, 0, 1),
|
||||
.float_rect = ruler_ctx.getLine(zoom_start.?, 1),
|
||||
.float_relative_to = ruler,
|
||||
});
|
||||
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = self.getLineOnRuler(view_id, graph_box, axis, zoom_start.?, 0, 1),
|
||||
.float_rect = graph_ctx.getLine(zoom_start.?, 1),
|
||||
.float_relative_to = graph_box,
|
||||
.parent = graph_box
|
||||
});
|
||||
@ -525,13 +483,13 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
||||
if (zoom_end != null) {
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, zoom_end.?, 0, 1),
|
||||
.float_rect = ruler_ctx.getLine(zoom_end.?, 1),
|
||||
.float_relative_to = ruler,
|
||||
});
|
||||
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = self.getLineOnRuler(view_id, graph_box, axis, zoom_end.?, 0, 1),
|
||||
.float_rect = graph_ctx.getLine(zoom_end.?, 1),
|
||||
.float_relative_to = graph_box,
|
||||
.parent = graph_box
|
||||
});
|
||||
@ -541,15 +499,7 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green.alpha(0.5),
|
||||
.float_relative_to = ruler,
|
||||
.float_rect = self.getRectOnRuler(
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
zoom_start.?,
|
||||
zoom_end.? - zoom_start.?,
|
||||
0,
|
||||
1
|
||||
)
|
||||
.float_rect = ruler_ctx.getRect(zoom_start.?, zoom_end.? - zoom_start.?, 0, 1),
|
||||
});
|
||||
}
|
||||
|
||||
@ -595,14 +545,13 @@ fn showView(self: *MainScreen, view_id: Id, height: UI.Sizing) !void {
|
||||
.key = UI.Key.initUsize(view_id.asInt()),
|
||||
.layout_direction = .top_to_bottom,
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = height
|
||||
.size_y = height,
|
||||
});
|
||||
view_box.beginChildren();
|
||||
defer view_box.endChildren();
|
||||
|
||||
const toolbar = ui.createBox(.{
|
||||
.layout_direction = .left_to_right,
|
||||
.layout_gap = 16,
|
||||
.background = srcery.hard_black,
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initFixed(.{ .pixels = ui.rem(2) })
|
||||
@ -611,9 +560,36 @@ fn showView(self: *MainScreen, view_id: Id, height: UI.Sizing) !void {
|
||||
toolbar.beginChildren();
|
||||
defer toolbar.endChildren();
|
||||
|
||||
const view = self.app.getView(view_id).?;
|
||||
var view_name: ?[]const u8 = null;
|
||||
|
||||
const view = self.app.getView(view_id).?;
|
||||
{
|
||||
const btn = ui.textButton("Settings");
|
||||
btn.background = srcery.hard_black;
|
||||
if (self.view_settings != null and self.view_settings.?.eql(view_id)) {
|
||||
btn.borders.bottom = .{
|
||||
.color = srcery.green,
|
||||
.size = 4
|
||||
};
|
||||
}
|
||||
|
||||
if (ui.signal(btn).clicked()) {
|
||||
if (self.view_settings != null and self.view_settings.?.eql(view_id)) {
|
||||
self.view_settings = null;
|
||||
} else {
|
||||
self.view_settings = view_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const btn = ui.textButton("Reset view");
|
||||
btn.background = srcery.hard_black;
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.pushViewMoveCommand(view_id, view.available_x_range, view.available_y_range);
|
||||
}
|
||||
}
|
||||
|
||||
if (view.reference == .channel) {
|
||||
const channel_id = view.reference.channel;
|
||||
const channel = self.app.getChannel(channel_id).?;
|
||||
@ -737,8 +713,8 @@ fn showView(self: *MainScreen, view_id: Id, height: UI.Sizing) !void {
|
||||
x_ruler = self.addRulerPlaceholder(ui.keyFromString("X ruler"), .X);
|
||||
}
|
||||
|
||||
self.showRuler(x_ruler, graph_box, view_id, .X);
|
||||
self.showRuler(y_ruler, graph_box, view_id, .Y);
|
||||
try self.showRuler(x_ruler, graph_box, view_id, .X);
|
||||
try self.showRuler(y_ruler, graph_box, view_id, .Y);
|
||||
|
||||
}
|
||||
}
|
||||
@ -936,22 +912,89 @@ pub fn showSidePanel(self: *MainScreen) !void {
|
||||
.right = .{ .color = srcery.hard_black, .size = 4 }
|
||||
},
|
||||
.layout_direction = .top_to_bottom,
|
||||
.padding = UI.Padding.all(ui.rem(1))
|
||||
.padding = UI.Padding.all(ui.rem(1)),
|
||||
.layout_gap = ui.rem(0.2)
|
||||
});
|
||||
container.beginChildren();
|
||||
defer container.endChildren();
|
||||
|
||||
const project = &self.app.project;
|
||||
const sample_rate = project.getSampleRate();
|
||||
|
||||
if (self.view_settings) |view_id| {
|
||||
const view = project.views.get(view_id) orelse return;
|
||||
|
||||
{
|
||||
const label = ui.label("Settings", .{});
|
||||
label.borders.bottom = .{
|
||||
.color = srcery.bright_white,
|
||||
.size = 1
|
||||
};
|
||||
}
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
|
||||
var sample_count: ?usize = null;
|
||||
switch (view.reference) {
|
||||
.channel => |channel_id| {
|
||||
const channel = project.channels.get(channel_id).?;
|
||||
const channel_name = utils.getBoundedStringZ(&channel.name);
|
||||
const channel_type = NIDaq.getChannelType(channel_name);
|
||||
const samples = channel.collected_samples.items;
|
||||
|
||||
_ = ui.label("Channel: {s}", .{ channel_name });
|
||||
|
||||
if (channel_type != null) {
|
||||
_ = ui.label("Type: {s}", .{ channel_type.?.name() });
|
||||
} else {
|
||||
_ = ui.label("Type: unknown", .{ });
|
||||
}
|
||||
|
||||
sample_count = samples.len;
|
||||
},
|
||||
.file => |file_id| {
|
||||
const file = project.files.get(file_id).?;
|
||||
|
||||
if (file.samples) |samples| {
|
||||
sample_count = samples.len;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (sample_count != null) {
|
||||
_ = ui.label("Samples: {d}", .{ sample_count.? });
|
||||
|
||||
var duration_str: []const u8 = "-";
|
||||
if (sample_rate != null) {
|
||||
const duration = @as(f64, @floatFromInt(sample_count.?)) / sample_rate.?;
|
||||
if (formatDuration(ui.frameAllocator(), duration)) |str| {
|
||||
duration_str = str;
|
||||
} else |_| {}
|
||||
}
|
||||
_ = ui.label("Duration: {s}", .{ duration_str });
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
{
|
||||
const label = ui.label("Project", .{});
|
||||
label.borders.bottom = .{
|
||||
.color = srcery.bright_white,
|
||||
.size = 1
|
||||
};
|
||||
}
|
||||
|
||||
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||
|
||||
{
|
||||
var placeholder: ?[]const u8 = null;
|
||||
if (project.getDefaultSampleRate()) |sample_rate| {
|
||||
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ sample_rate });
|
||||
if (project.getDefaultSampleRate()) |default_sample_rate| {
|
||||
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
|
||||
}
|
||||
|
||||
var initial: ?[]const u8 = null;
|
||||
if (project.sample_rate) |sample_rate| {
|
||||
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ sample_rate });
|
||||
if (project.sample_rate) |selected_sample_rate| {
|
||||
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ selected_sample_rate });
|
||||
}
|
||||
|
||||
_ = ui.label("Sample rate", .{});
|
||||
@ -960,13 +1003,14 @@ pub fn showSidePanel(self: *MainScreen) !void {
|
||||
.storage = &self.sample_rate_input,
|
||||
.placeholder = placeholder,
|
||||
.initial = initial,
|
||||
.invalid = self.parsed_sample_rate != project.sample_rate
|
||||
.invalid = self.parsed_sample_rate != project.sample_rate,
|
||||
.editable = !self.app.isCollectionInProgress()
|
||||
});
|
||||
project.sample_rate = self.parsed_sample_rate;
|
||||
|
||||
if (project.getAllowedSampleRates()) |allowed_sample_rates| {
|
||||
if (project.sample_rate) |sample_rate| {
|
||||
if (!allowed_sample_rates.hasInclusive(sample_rate)) {
|
||||
if (project.sample_rate) |selected_sample_rate| {
|
||||
if (!allowed_sample_rates.hasInclusive(selected_sample_rate)) {
|
||||
project.sample_rate = null;
|
||||
}
|
||||
}
|
||||
@ -1117,6 +1161,8 @@ pub fn tick(self: *MainScreen) !void {
|
||||
self.closeModal();
|
||||
} else if (self.fullscreen_view != null) {
|
||||
self.fullscreen_view = null;
|
||||
} else if (self.view_settings != null) {
|
||||
self.view_settings = null;
|
||||
} else {
|
||||
self.app.should_close = true;
|
||||
}
|
||||
|
26
src/ui.zig
26
src/ui.zig
@ -476,6 +476,11 @@ pub const Box = struct {
|
||||
|
||||
pub const Flags = std.EnumSet(Flag);
|
||||
|
||||
pub const Draw = struct {
|
||||
ctx: ?*anyopaque = null,
|
||||
do: *const fn(ctx: ?*anyopaque, box: *Box) void
|
||||
};
|
||||
|
||||
const max_wrapped_lines = 64;
|
||||
|
||||
ui: *UI,
|
||||
@ -507,6 +512,7 @@ pub const Box = struct {
|
||||
scientific_number: ?f64 = null,
|
||||
scientific_precision: u32 = 1,
|
||||
float_relative_to: ?*Box = null,
|
||||
draw: ?Draw = null,
|
||||
|
||||
// Variables that you probably shouldn't be touching
|
||||
last_used_frame: u64 = 0,
|
||||
@ -725,6 +731,7 @@ pub const BoxOptions = struct {
|
||||
float_relative_to: ?*Box = null,
|
||||
parent: ?*UI.Box = null,
|
||||
texture_color: ?rl.Color = null,
|
||||
draw: ?Box.Draw = null
|
||||
};
|
||||
|
||||
pub const root_box_key = Key.initString(0, "$root$");
|
||||
@ -1531,6 +1538,7 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
||||
.scientific_precision = opts.scientific_precision orelse 1,
|
||||
.float_relative_to = opts.float_relative_to,
|
||||
.texture_color = opts.texture_color,
|
||||
.draw = opts.draw,
|
||||
|
||||
.last_used_frame = self.frame_index,
|
||||
.key = key,
|
||||
@ -1683,6 +1691,10 @@ fn drawBox(self: *UI, box: *Box) void {
|
||||
);
|
||||
}
|
||||
|
||||
if (box.draw) |box_draw| {
|
||||
box_draw.do(box_draw.ctx, box);
|
||||
}
|
||||
|
||||
const alignment_x_coeff = box.alignment.x.getCoefficient();
|
||||
const alignment_y_coeff = box.alignment.y.getCoefficient();
|
||||
|
||||
@ -2206,6 +2218,7 @@ pub const TextInputStorage = struct {
|
||||
pub const TextInputOptions = struct {
|
||||
key: Key,
|
||||
storage: *TextInputStorage,
|
||||
editable: bool = true,
|
||||
|
||||
initial: ?[]const u8 = null,
|
||||
placeholder: ?[]const u8 = null,
|
||||
@ -2216,6 +2229,7 @@ pub const NumberInputOptions = struct {
|
||||
key: Key,
|
||||
storage: *TextInputStorage,
|
||||
invalid: bool = false,
|
||||
editable: bool = true,
|
||||
|
||||
initial: ?[]const u8 = null,
|
||||
placeholder: ?[]const u8 = null,
|
||||
@ -2423,7 +2437,7 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||
}
|
||||
|
||||
const container_signal = self.signal(container);
|
||||
if (container_signal.hot) {
|
||||
if (opts.editable and container_signal.hot) {
|
||||
container.borders = UI.Borders.all(.{
|
||||
.color = srcery.red,
|
||||
.size = 2
|
||||
@ -2467,7 +2481,7 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||
}
|
||||
}
|
||||
|
||||
if (container_signal.active) {
|
||||
if (opts.editable and container_signal.active) {
|
||||
storage.editing = true;
|
||||
}
|
||||
|
||||
@ -2623,7 +2637,6 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||
}
|
||||
try storage.insertSingle(storage.cursor_start, @intCast(char));
|
||||
|
||||
|
||||
no_blinking = true;
|
||||
}
|
||||
}
|
||||
@ -2640,6 +2653,10 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||
if (container_signal.clicked_outside and !container_signal.is_mouse_inside) {
|
||||
storage.editing = false;
|
||||
}
|
||||
|
||||
if (!opts.editable) {
|
||||
storage.editing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2651,7 +2668,8 @@ pub fn numberInput(self: *UI, T: type, opts: NumberInputOptions) !?T {
|
||||
.storage = opts.storage,
|
||||
.initial = opts.initial,
|
||||
.text_color = opts.text_color,
|
||||
.placeholder = opts.placeholder
|
||||
.placeholder = opts.placeholder,
|
||||
.editable = opts.editable
|
||||
};
|
||||
|
||||
var is_invalid = opts.invalid;
|
||||
|
44
tools/generate.zig
Normal file
44
tools/generate.zig
Normal file
@ -0,0 +1,44 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn show_usage() void {
|
||||
std.debug.print("Usage: zig build generate <sample-rate> <sample-count> <filename>\n", .{});
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
var args = try std.process.argsWithAllocator(allocator);
|
||||
defer args.deinit();
|
||||
|
||||
_ = args.next();
|
||||
|
||||
const sample_rate_str = args.next() orelse {
|
||||
show_usage();
|
||||
std.process.exit(1);
|
||||
};
|
||||
|
||||
const sample_count_str = args.next() orelse {
|
||||
show_usage();
|
||||
std.process.exit(1);
|
||||
};
|
||||
|
||||
const filename = args.next() orelse {
|
||||
show_usage();
|
||||
std.process.exit(1);
|
||||
};
|
||||
|
||||
const sample_rate = try std.fmt.parseFloat(f64, sample_rate_str);
|
||||
const sample_count = try std.fmt.parseInt(u32, sample_count_str, 10);
|
||||
|
||||
const f = try std.fs.cwd().createFile(filename, .{ .exclusive = true });
|
||||
defer f.close();
|
||||
|
||||
for (0..sample_count) |i| {
|
||||
const i_f64: f64 = @floatFromInt(i);
|
||||
const sample: f64 = std.math.sin( i_f64 / sample_rate * std.math.pi * 2 ) * 10;
|
||||
const sample_bytes = std.mem.toBytes(sample);
|
||||
try f.writeAll(&sample_bytes);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user