refactor in preparation for saving state to a file
This commit is contained in:
parent
c588956226
commit
d2b4942fa0
1358
src/app.zig
1358
src/app.zig
File diff suppressed because it is too large
Load Diff
7
src/constants.zig
Normal file
7
src/constants.zig
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
pub const max_files = 32;
|
||||
|
||||
pub const max_channels = 32;
|
||||
|
||||
pub const max_views = 64;
|
@ -20,10 +20,28 @@ comptime {
|
||||
}
|
||||
|
||||
pub const ViewOptions = struct {
|
||||
x_range: RangeF64,
|
||||
y_range: RangeF64,
|
||||
x_range: RangeF64 = RangeF64.init(0, 0),
|
||||
y_range: RangeF64 = RangeF64.init(0, 0),
|
||||
|
||||
color: rl.Color = srcery.red,
|
||||
|
||||
fn writeStruct(self: ViewOptions, writer: anytype) !void {
|
||||
_ = self;
|
||||
_ = writer;
|
||||
// try writer.writeStructEndian(self.id, file_endian);
|
||||
// try writer.writeStructEndian(self.channel_name.constSlice(), file_endian);
|
||||
}
|
||||
|
||||
fn readStruct(reader: anytype) !ViewOptions {
|
||||
_ = reader;
|
||||
// const id = try reader.readStructEndian(Id, file_endian);
|
||||
// const channel_name = try reader.readStructEndian([]const u8, file_endian);
|
||||
|
||||
return ViewOptions{
|
||||
.x_range = RangeF64.init(0, 0),
|
||||
.y_range = RangeF64.init(0, 0),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Cache = struct {
|
||||
@ -35,7 +53,7 @@ pub const Cache = struct {
|
||||
texture: ?rl.RenderTexture2D = null,
|
||||
key: ?Key = null,
|
||||
|
||||
pub fn deinit(self: *Cache) void {
|
||||
pub fn clear(self: *Cache) void {
|
||||
if (self.texture) |texture| {
|
||||
texture.unload();
|
||||
self.texture = null;
|
||||
|
22
src/main.zig
22
src/main.zig
@ -71,7 +71,9 @@ pub fn main() !void {
|
||||
raylib_h.SetTraceLogCallback(raylibTraceLogCallback);
|
||||
rl.setTraceLogLevel(toRaylibLogLevel(std.options.log_level));
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{
|
||||
.thread_safe = true
|
||||
}){};
|
||||
const allocator = gpa.allocator();
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
@ -100,8 +102,22 @@ pub fn main() !void {
|
||||
defer app.deinit();
|
||||
|
||||
if (builtin.mode == .Debug) {
|
||||
try app.appendChannelFromDevice("Dev1/ai0");
|
||||
try app.appendChannelFromDevice("Dev3/ao0");
|
||||
// const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
|
||||
// defer allocator.free(cwd_path);
|
||||
// try app.loadProject(cwd_path);
|
||||
|
||||
_ = 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")
|
||||
});
|
||||
|
||||
// try app.appendChannelFromDevice("Dev1/ai0");
|
||||
// try app.appendChannelFromDevice("Dev3/ao0");
|
||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin");
|
||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
||||
|
@ -5,7 +5,7 @@ pub const c = Api.c;
|
||||
const assert = std.debug.assert;
|
||||
const log = std.log.scoped(.ni_daq);
|
||||
|
||||
const max_device_name_size = 255;
|
||||
pub const max_device_name_size = 255;
|
||||
const max_task_name_size = 255;
|
||||
|
||||
pub const max_channel_name_size = count: {
|
||||
|
@ -4,7 +4,7 @@ const remap_number = @import("./utils.zig").remap;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub fn Range(Number: type) type {
|
||||
return struct {
|
||||
return packed struct {
|
||||
const Self = @This();
|
||||
|
||||
lower: Number,
|
||||
|
@ -13,15 +13,15 @@ const NIDaq = @import("../ni-daq/root.zig");
|
||||
const MainScreen = @This();
|
||||
|
||||
const log = std.log.scoped(.main_screen);
|
||||
const ChannelView = App.ChannelView;
|
||||
const assert = std.debug.assert;
|
||||
const remap = utils.remap;
|
||||
const Id = App.Id;
|
||||
|
||||
const zoom_speed = 0.1;
|
||||
const ruler_size = UI.Sizing.initFixed(.{ .pixels = 32 });
|
||||
|
||||
const ChannelCommand = struct {
|
||||
channel: *ChannelView,
|
||||
const ViewCommand = struct {
|
||||
view_id: Id,
|
||||
updated_at_ns: i128,
|
||||
action: union(enum) {
|
||||
move_and_zoom: struct {
|
||||
@ -32,23 +32,23 @@ const ChannelCommand = struct {
|
||||
};
|
||||
|
||||
app: *App,
|
||||
fullscreen_channel: ?*ChannelView = null,
|
||||
fullscreen_view: ?Id = null,
|
||||
|
||||
axis_zoom: ?struct {
|
||||
channel: *ChannelView,
|
||||
view_id: Id,
|
||||
axis: UI.Axis,
|
||||
start: f64,
|
||||
} = null,
|
||||
|
||||
// TODO: Redo
|
||||
channel_undo_stack: std.BoundedArray(ChannelCommand, 100) = .{},
|
||||
view_undo_stack: std.BoundedArray(ViewCommand, 100) = .{},
|
||||
|
||||
protocol_modal: ?*ChannelView = null,
|
||||
protocol_modal: ?Id = null,
|
||||
frequency_input: UI.TextInputStorage,
|
||||
amplitude_input: UI.TextInputStorage,
|
||||
protocol_error_message: ?[]const u8 = null,
|
||||
protocol_graph_cache: Graph.Cache = .{},
|
||||
preview_samples: std.ArrayList(f64),
|
||||
preview_samples: std.ArrayListUnmanaged(f64) = .{},
|
||||
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
|
||||
|
||||
pub fn init(app: *App) !MainScreen {
|
||||
@ -58,7 +58,6 @@ pub fn init(app: *App) !MainScreen {
|
||||
.app = app,
|
||||
.frequency_input = UI.TextInputStorage.init(allocator),
|
||||
.amplitude_input = UI.TextInputStorage.init(allocator),
|
||||
.preview_samples = std.ArrayList(f64).init(allocator)
|
||||
};
|
||||
|
||||
try self.frequency_input.setText("10");
|
||||
@ -68,16 +67,21 @@ pub fn init(app: *App) !MainScreen {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *MainScreen) void {
|
||||
const allocator = self.app.allocator;
|
||||
|
||||
self.frequency_input.deinit();
|
||||
self.amplitude_input.deinit();
|
||||
self.preview_samples.deinit();
|
||||
self.preview_samples.clearAndFree(allocator);
|
||||
|
||||
self.clearProtocolErrorMessage();
|
||||
}
|
||||
|
||||
fn pushChannelMoveCommand(self: *MainScreen, channel_view: *ChannelView, x_range: RangeF64, y_range: RangeF64) void {
|
||||
fn pushViewMoveCommand(self: *MainScreen, view_id: Id, x_range: RangeF64, y_range: RangeF64) void {
|
||||
const view = self.app.getView(view_id) orelse return;
|
||||
const view_rect = &view.graph_opts;
|
||||
|
||||
const now_ns = std.time.nanoTimestamp();
|
||||
var undo_stack = &self.channel_undo_stack;
|
||||
var undo_stack = &self.view_undo_stack;
|
||||
var push_new_command = true;
|
||||
|
||||
if (undo_stack.len > 0) {
|
||||
@ -88,15 +92,13 @@ fn pushChannelMoveCommand(self: *MainScreen, channel_view: *ChannelView, x_range
|
||||
}
|
||||
}
|
||||
|
||||
var view_rect = &channel_view.view_rect;
|
||||
|
||||
if (push_new_command) {
|
||||
if (undo_stack.unusedCapacitySlice().len == 0) {
|
||||
_ = undo_stack.orderedRemove(0);
|
||||
}
|
||||
|
||||
undo_stack.appendAssumeCapacity(ChannelCommand{
|
||||
.channel = channel_view,
|
||||
undo_stack.appendAssumeCapacity(ViewCommand{
|
||||
.view_id = view_id,
|
||||
.updated_at_ns = now_ns,
|
||||
.action = .{
|
||||
.move_and_zoom = .{
|
||||
@ -109,24 +111,39 @@ fn pushChannelMoveCommand(self: *MainScreen, channel_view: *ChannelView, x_range
|
||||
|
||||
view_rect.x_range = x_range;
|
||||
view_rect.y_range = y_range;
|
||||
channel_view.follow = false;
|
||||
view.follow = false;
|
||||
}
|
||||
|
||||
fn pushChannelMoveCommandAxis(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis, view_range: RangeF64) void {
|
||||
fn pushViewMoveCommandAxis(self: *MainScreen, view_id: Id, axis: UI.Axis, view_range: RangeF64) void {
|
||||
const view = self.app.getView(view_id) orelse return;
|
||||
const view_rect = &view.graph_opts;
|
||||
|
||||
if (axis == .X) {
|
||||
const view_rect = &channel_view.view_rect;
|
||||
self.pushChannelMoveCommand(channel_view, view_range, view_rect.y_range);
|
||||
self.pushViewMoveCommand(view_id, view_range, view_rect.y_range);
|
||||
} else {
|
||||
const view_rect = &channel_view.view_rect;
|
||||
self.pushChannelMoveCommand(channel_view, view_rect.x_range, view_range);
|
||||
self.pushViewMoveCommand(view_id, view_rect.x_range, view_range);
|
||||
}
|
||||
}
|
||||
|
||||
fn showChannelViewGraph(self: *MainScreen, channel_view: *ChannelView) *UI.Box {
|
||||
fn undoLastMoveCommand(self: *MainScreen) void {
|
||||
const command = self.view_undo_stack.popOrNull() orelse return;
|
||||
const view = self.app.getView(command.view_id) orelse return;
|
||||
const view_rect = &view.graph_opts;
|
||||
|
||||
switch (command.action) {
|
||||
.move_and_zoom => |args| {
|
||||
view_rect.x_range = args.before_x;
|
||||
view_rect.y_range = args.before_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn showChannelViewGraph(self: *MainScreen, view_id: Id) *UI.Box {
|
||||
var ui = &self.app.ui;
|
||||
|
||||
const samples = self.app.getChannelSamples(channel_view);
|
||||
const view_rect: *Graph.ViewOptions = &channel_view.view_rect;
|
||||
const view = self.app.getView(view_id).?;
|
||||
const samples = self.app.getViewSamples(view_id);
|
||||
const view_opts = &view.graph_opts;
|
||||
|
||||
const graph_box = ui.createBox(.{
|
||||
.key = ui.keyFromString("Graph"),
|
||||
@ -151,18 +168,18 @@ fn showChannelViewGraph(self: *MainScreen, channel_view: *ChannelView) *UI.Box {
|
||||
const mouse_y_range = RangeF64.init(0, graph_rect.height);
|
||||
|
||||
if (signal.hot) {
|
||||
sample_index_under_mouse = mouse_x_range.remapTo(view_rect.x_range, signal.relative_mouse.x);
|
||||
sample_value_under_mouse = mouse_y_range.remapTo(view_rect.y_range, signal.relative_mouse.y);
|
||||
sample_index_under_mouse = mouse_x_range.remapTo(view_opts.x_range, signal.relative_mouse.x);
|
||||
sample_value_under_mouse = mouse_y_range.remapTo(view_opts.y_range, signal.relative_mouse.y);
|
||||
}
|
||||
|
||||
if (signal.dragged()) {
|
||||
const x_offset = mouse_x_range.remapTo(RangeF64.init(0, view_rect.x_range.size()), signal.drag.x);
|
||||
const y_offset = mouse_y_range.remapTo(RangeF64.init(0, view_rect.y_range.size()), signal.drag.y);
|
||||
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);
|
||||
|
||||
self.pushChannelMoveCommand(
|
||||
channel_view,
|
||||
view_rect.x_range.sub(x_offset),
|
||||
view_rect.y_range.add(y_offset)
|
||||
self.pushViewMoveCommand(
|
||||
view_id,
|
||||
view_opts.x_range.sub(x_offset),
|
||||
view_opts.y_range.add(y_offset)
|
||||
);
|
||||
}
|
||||
|
||||
@ -174,23 +191,23 @@ fn showChannelViewGraph(self: *MainScreen, channel_view: *ChannelView) *UI.Box {
|
||||
scale_factor += zoom_speed;
|
||||
}
|
||||
|
||||
self.pushChannelMoveCommand(
|
||||
channel_view,
|
||||
view_rect.x_range.zoom(sample_index_under_mouse.?, scale_factor),
|
||||
view_rect.y_range.zoom(sample_value_under_mouse.?, scale_factor)
|
||||
self.pushViewMoveCommand(
|
||||
view_id,
|
||||
view_opts.x_range.zoom(sample_index_under_mouse.?, scale_factor),
|
||||
view_opts.y_range.zoom(sample_value_under_mouse.?, scale_factor)
|
||||
);
|
||||
}
|
||||
|
||||
if (signal.flags.contains(.middle_clicked)) {
|
||||
self.pushChannelMoveCommand(channel_view, channel_view.x_range, channel_view.y_range);
|
||||
self.pushViewMoveCommand(view_id, view.available_x_range, view.available_y_range);
|
||||
}
|
||||
|
||||
Graph.drawCached(&channel_view.view_cache, graph_box.persistent.size, view_rect.*, samples);
|
||||
if (channel_view.view_cache.texture) |texture| {
|
||||
Graph.drawCached(&view.graph_cache, graph_box.persistent.size, view_opts.*, samples);
|
||||
if (view.graph_cache.texture) |texture| {
|
||||
graph_box.texture = texture.texture;
|
||||
}
|
||||
|
||||
if (view_rect.x_range.size() == 0 or view_rect.y_range.size() == 0) {
|
||||
if (view_opts.x_range.size() == 0 or view_opts.y_range.size() == 0) {
|
||||
graph_box.setText("<Empty>");
|
||||
graph_box.text_color = srcery.hard_black;
|
||||
graph_box.font = .{
|
||||
@ -203,7 +220,8 @@ fn showChannelViewGraph(self: *MainScreen, channel_view: *ChannelView) *UI.Box {
|
||||
}
|
||||
|
||||
fn getLineOnRuler(
|
||||
channel_view: *ChannelView,
|
||||
self: *MainScreen,
|
||||
view_id: Id,
|
||||
ruler: *UI.Box,
|
||||
axis: UI.Axis,
|
||||
|
||||
@ -211,16 +229,16 @@ fn getLineOnRuler(
|
||||
cross_axis_pos: f64,
|
||||
cross_axis_size: f64
|
||||
) rl.Rectangle {
|
||||
const view = self.app.getView(view_id).?;
|
||||
const shown_size = view.getGraphView(axis).size();
|
||||
|
||||
const view_rect = channel_view.view_rect;
|
||||
|
||||
const along_axis_size = switch (axis) {
|
||||
.X => view_rect.x_range.size()/ruler.persistent.size.x,
|
||||
.Y => view_rect.y_range.size()/ruler.persistent.size.y,
|
||||
const along_axis_size = shown_size / switch (axis) {
|
||||
.X => ruler.persistent.size.x,
|
||||
.Y => ruler.persistent.size.y,
|
||||
};
|
||||
|
||||
return getRectOnRuler(
|
||||
channel_view,
|
||||
return self.getRectOnRuler(
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
|
||||
@ -232,7 +250,8 @@ fn getLineOnRuler(
|
||||
}
|
||||
|
||||
fn getRectOnRuler(
|
||||
channel_view: *ChannelView,
|
||||
self: *MainScreen,
|
||||
view_id: Id,
|
||||
ruler: *UI.Box,
|
||||
axis: UI.Axis,
|
||||
|
||||
@ -246,7 +265,9 @@ fn getRectOnRuler(
|
||||
const rect = ruler.rect();
|
||||
const rect_height: f64 = @floatCast(rect.height);
|
||||
const rect_width: f64 = @floatCast(rect.width);
|
||||
const view_range = channel_view.getViewRange(axis);
|
||||
|
||||
const view = self.app.getView(view_id).?;
|
||||
const view_range = view.getGraphView(axis).*;
|
||||
|
||||
if (axis == .X) {
|
||||
const width_range = RangeF64.init(0, rect.width);
|
||||
@ -283,7 +304,7 @@ fn getRectOnRuler(
|
||||
|
||||
fn showRulerTicksRange(
|
||||
self: *MainScreen,
|
||||
channel_view: *ChannelView,
|
||||
view_id: Id,
|
||||
ruler: *UI.Box,
|
||||
axis: UI.Axis,
|
||||
|
||||
@ -297,15 +318,17 @@ fn showRulerTicksRange(
|
||||
while (marker < to) : (marker += step) {
|
||||
_ = self.app.ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = getLineOnRuler(channel_view, ruler, axis, marker, 0, marker_size),
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, marker, 0, marker_size),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn showRulerTicks(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis) void {
|
||||
const view_range = channel_view.getViewRange(axis);
|
||||
const full_range = channel_view.getSampleRange(axis);
|
||||
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().?;
|
||||
@ -351,7 +374,7 @@ fn showRulerTicks(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis)
|
||||
{
|
||||
_ = self.app.ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = getLineOnRuler(channel_view, ruler, axis, full_range.lower, 0, 0.75),
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, full_range.lower, 0, 0.75),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
}
|
||||
@ -359,7 +382,7 @@ fn showRulerTicks(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis)
|
||||
{
|
||||
_ = self.app.ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = getLineOnRuler(channel_view, ruler, axis, full_range.upper, 0, 0.75),
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, full_range.upper, 0, 0.75),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
}
|
||||
@ -367,15 +390,15 @@ fn showRulerTicks(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis)
|
||||
if (full_range.hasExclusive(0)) {
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = getLineOnRuler(channel_view, ruler, axis, 0, 0, 0.75),
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, 0, 0, 0.75),
|
||||
.float_relative_to = ruler
|
||||
});
|
||||
}
|
||||
|
||||
const ticks_range = view_range.grow(step).intersectPositive(full_range.*);
|
||||
const ticks_range = view_range.grow(step).intersectPositive(full_range);
|
||||
|
||||
self.showRulerTicksRange(
|
||||
channel_view,
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
utils.roundNearestTowardZero(f64, ticks_range.lower, step) + step/2,
|
||||
@ -385,7 +408,7 @@ fn showRulerTicks(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis)
|
||||
);
|
||||
|
||||
self.showRulerTicksRange(
|
||||
channel_view,
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
utils.roundNearestTowardZero(f64, ticks_range.lower, step),
|
||||
@ -415,13 +438,15 @@ 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, channel_view: *ChannelView, axis: UI.Axis) void {
|
||||
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;
|
||||
|
||||
ruler.beginChildren();
|
||||
defer ruler.endChildren();
|
||||
|
||||
self.showRulerTicks(channel_view, axis);
|
||||
self.showRulerTicks(view_id, axis);
|
||||
|
||||
const signal = ui.signal(ruler);
|
||||
const mouse_position = switch (axis) {
|
||||
@ -432,7 +457,7 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
.X => RangeF64.init(0, ruler.persistent.size.x),
|
||||
.Y => RangeF64.init(0, ruler.persistent.size.y)
|
||||
};
|
||||
const view_range = channel_view.getViewRange(axis);
|
||||
const view_range = view.getGraphView(axis);
|
||||
const mouse_position_on_graph = mouse_range.remapTo(view_range.*, mouse_position);
|
||||
|
||||
var zoom_start: ?f64 = null;
|
||||
@ -440,7 +465,7 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
|
||||
var is_zooming: bool = false;
|
||||
if (self.axis_zoom) |axis_zoom| {
|
||||
is_zooming = axis_zoom.channel == channel_view and axis_zoom.axis == axis;
|
||||
is_zooming = axis_zoom.view_id.eql(view_id) and axis_zoom.axis == axis;
|
||||
}
|
||||
|
||||
if (signal.hot and view_range.size() > 0) {
|
||||
@ -448,12 +473,14 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
mouse_tooltip.beginChildren();
|
||||
defer mouse_tooltip.endChildren();
|
||||
|
||||
if (channel_view.getSampleRange(axis).hasInclusive(mouse_position_on_graph)) {
|
||||
if (axis == .Y and channel_view.unit != null) {
|
||||
const unit_name = channel_view.unit.?.name() orelse "Unknown";
|
||||
const project = &self.app.project;
|
||||
|
||||
if (view.getAvailableView(axis).hasInclusive(mouse_position_on_graph)) {
|
||||
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 channel_view.sample_rate != null) {
|
||||
const sample_rate = channel_view.sample_rate.?;
|
||||
} 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 {
|
||||
_ = ui.label("{d:.3}", .{mouse_position_on_graph});
|
||||
@ -467,7 +494,7 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
self.axis_zoom = .{
|
||||
.axis = axis,
|
||||
.start = mouse_position_on_graph,
|
||||
.channel = channel_view
|
||||
.view_id = view_id
|
||||
};
|
||||
}
|
||||
|
||||
@ -479,13 +506,13 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
if (zoom_start != null) {
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = getLineOnRuler(channel_view, ruler, axis, zoom_start.?, 0, 1),
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, zoom_start.?, 0, 1),
|
||||
.float_relative_to = ruler,
|
||||
});
|
||||
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = getLineOnRuler(channel_view, graph_box, axis, zoom_start.?, 0, 1),
|
||||
.float_rect = self.getLineOnRuler(view_id, graph_box, axis, zoom_start.?, 0, 1),
|
||||
.float_relative_to = graph_box,
|
||||
.parent = graph_box
|
||||
});
|
||||
@ -494,13 +521,13 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
if (zoom_end != null) {
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = getLineOnRuler(channel_view, ruler, axis, zoom_end.?, 0, 1),
|
||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, zoom_end.?, 0, 1),
|
||||
.float_relative_to = ruler,
|
||||
});
|
||||
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
.float_rect = getLineOnRuler(channel_view, graph_box, axis, zoom_end.?, 0, 1),
|
||||
.float_rect = self.getLineOnRuler(view_id, graph_box, axis, zoom_end.?, 0, 1),
|
||||
.float_relative_to = graph_box,
|
||||
.parent = graph_box
|
||||
});
|
||||
@ -510,8 +537,8 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
_ = ui.createBox(.{
|
||||
.background = srcery.green.alpha(0.5),
|
||||
.float_relative_to = ruler,
|
||||
.float_rect = getRectOnRuler(
|
||||
channel_view,
|
||||
.float_rect = self.getRectOnRuler(
|
||||
view_id,
|
||||
ruler,
|
||||
axis,
|
||||
zoom_start.?,
|
||||
@ -530,7 +557,7 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
scale_factor += zoom_speed;
|
||||
}
|
||||
const new_view_range = view_range.zoom(mouse_position_on_graph, scale_factor);
|
||||
self.pushChannelMoveCommandAxis(channel_view, axis, new_view_range);
|
||||
self.pushViewMoveCommandAxis(view_id, axis, new_view_range);
|
||||
}
|
||||
|
||||
if (is_zooming and signal.flags.contains(.left_released)) {
|
||||
@ -548,26 +575,26 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view
|
||||
new_view_range = new_view_range.flip();
|
||||
}
|
||||
|
||||
self.pushChannelMoveCommandAxis(channel_view, axis, new_view_range);
|
||||
self.pushViewMoveCommandAxis(view_id, axis, new_view_range);
|
||||
}
|
||||
}
|
||||
self.axis_zoom = null;
|
||||
}
|
||||
}
|
||||
|
||||
fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Sizing) !void {
|
||||
fn showView(self: *MainScreen, view_id: Id, height: UI.Sizing) !void {
|
||||
var ui = &self.app.ui;
|
||||
|
||||
const show_ruler = true;
|
||||
|
||||
const channel_view_box = ui.createBox(.{
|
||||
.key = UI.Key.initPtr(channel_view),
|
||||
const view_box = ui.createBox(.{
|
||||
.key = UI.Key.initUsize(view_id.asInt()),
|
||||
.layout_direction = .top_to_bottom,
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = height
|
||||
});
|
||||
channel_view_box.beginChildren();
|
||||
defer channel_view_box.endChildren();
|
||||
view_box.beginChildren();
|
||||
defer view_box.endChildren();
|
||||
|
||||
const toolbar = ui.createBox(.{
|
||||
.layout_direction = .left_to_right,
|
||||
@ -580,21 +607,26 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz
|
||||
toolbar.beginChildren();
|
||||
defer toolbar.endChildren();
|
||||
|
||||
if (self.app.getChannelSourceDevice(channel_view)) |device_channel| {
|
||||
const channel_name = device_channel.getChannelName();
|
||||
var view_name: ?[]const u8 = null;
|
||||
|
||||
const view = self.app.getView(view_id).?;
|
||||
if (view.reference == .channel) {
|
||||
const channel_id = view.reference.channel;
|
||||
const channel = self.app.getChannel(channel_id).?;
|
||||
const channel_name = utils.getBoundedStringZ(&channel.name);
|
||||
const channel_type = NIDaq.getChannelType(channel_name).?;
|
||||
|
||||
{
|
||||
const follow = ui.textButton("Follow");
|
||||
follow.background = srcery.hard_black;
|
||||
if (channel_view.follow) {
|
||||
if (view.follow) {
|
||||
follow.borders = UI.Borders.bottom(.{
|
||||
.color = srcery.green,
|
||||
.size = 4
|
||||
});
|
||||
}
|
||||
if (ui.signal(follow).clicked()) {
|
||||
channel_view.follow = !channel_view.follow;
|
||||
view.follow = !view.follow;
|
||||
}
|
||||
}
|
||||
|
||||
@ -605,17 +637,17 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz
|
||||
|
||||
const signal = ui.signal(button);
|
||||
if (signal.clicked()) {
|
||||
if (self.app.isDeviceChannelActive(channel_view)) {
|
||||
self.app.deferred_actions.appendAssumeCapacity(.{
|
||||
.deactivate = channel_view
|
||||
if (self.app.isChannelOutputing(channel_id)) {
|
||||
self.app.pushCommand(.{
|
||||
.stop_output = channel_id
|
||||
});
|
||||
} else {
|
||||
try self.openProtocolModal(channel_view);
|
||||
try self.openProtocolModal(channel_id);
|
||||
}
|
||||
}
|
||||
|
||||
var color = rl.Color.white;
|
||||
if (self.app.isDeviceChannelActive(channel_view)) {
|
||||
if (self.app.isChannelOutputing(channel_id)) {
|
||||
color = srcery.red;
|
||||
}
|
||||
|
||||
@ -628,22 +660,29 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz
|
||||
}
|
||||
}
|
||||
|
||||
view_name = channel_name;
|
||||
} else if (view.reference == .file) {
|
||||
const file_id = view.reference.file;
|
||||
const file = self.app.getFile(file_id).?;
|
||||
|
||||
view_name = std.fs.path.stem(file.path);
|
||||
}
|
||||
|
||||
if (view_name) |text| {
|
||||
_ = ui.createBox(.{
|
||||
.size_x = UI.Sizing.initGrowFull()
|
||||
});
|
||||
|
||||
{
|
||||
const label = ui.label("{s}", .{channel_name});
|
||||
const label = ui.label("{s}", .{text});
|
||||
label.size.y = UI.Sizing.initGrowFull();
|
||||
label.alignment.x = .center;
|
||||
label.alignment.y = .center;
|
||||
label.padding = UI.Padding.horizontal(ui.rem(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!show_ruler) {
|
||||
_ = self.showChannelViewGraph(channel_view);
|
||||
_ = self.showChannelViewGraph(view_id);
|
||||
|
||||
} else {
|
||||
var graph_box: *UI.Box = undefined;
|
||||
@ -661,7 +700,7 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz
|
||||
|
||||
y_ruler = self.addRulerPlaceholder(ui.keyFromString("Y ruler"), .Y);
|
||||
|
||||
graph_box = self.showChannelViewGraph(channel_view);
|
||||
graph_box = self.showChannelViewGraph(view_id);
|
||||
}
|
||||
|
||||
{
|
||||
@ -684,35 +723,37 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz
|
||||
.texture_size = .{ .x = 28, .y = 28 }
|
||||
});
|
||||
if (ui.signal(fullscreen).clicked()) {
|
||||
if (self.fullscreen_channel != null and self.fullscreen_channel.? == channel_view) {
|
||||
self.fullscreen_channel = null;
|
||||
if (self.fullscreen_view != null and self.fullscreen_view.?.eql(view_id)) {
|
||||
self.fullscreen_view = null;
|
||||
} else {
|
||||
self.fullscreen_channel = channel_view;
|
||||
self.fullscreen_view = view_id;
|
||||
}
|
||||
}
|
||||
|
||||
x_ruler = self.addRulerPlaceholder(ui.keyFromString("X ruler"), .X);
|
||||
}
|
||||
|
||||
self.showRuler(x_ruler, graph_box, channel_view, .X);
|
||||
self.showRuler(y_ruler, graph_box, channel_view, .Y);
|
||||
self.showRuler(x_ruler, graph_box, view_id, .X);
|
||||
self.showRuler(y_ruler, graph_box, view_id, .Y);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn openProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
|
||||
self.protocol_modal = channel_view;
|
||||
self.protocol_graph_cache.deinit();
|
||||
fn openProtocolModal(self: *MainScreen, channel_id: Id) !void {
|
||||
self.protocol_modal = channel_id;
|
||||
self.protocol_graph_cache.clear();
|
||||
}
|
||||
|
||||
fn closeModal(self: *MainScreen) void {
|
||||
self.protocol_modal = null;
|
||||
}
|
||||
|
||||
pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
|
||||
pub fn showProtocolModal(self: *MainScreen, channel_id: Id) !void {
|
||||
var ui = &self.app.ui;
|
||||
|
||||
const device_channel = self.app.getChannelSourceDevice(channel_view).?;
|
||||
const allocator = self.app.allocator;
|
||||
const channel = self.app.getChannel(channel_id) orelse return;
|
||||
const sample_rate = self.app.project.getSampleRate() orelse return;
|
||||
|
||||
const container = ui.createBox(.{
|
||||
.key = ui.keyFromString("Protocol modal"),
|
||||
@ -814,7 +855,7 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
|
||||
}
|
||||
|
||||
if (self.protocol_error_message == null and any_input_modified) {
|
||||
try App.DeviceChannel.generateSine(&self.preview_samples, channel_view.sample_rate.?, frequency, amplitude);
|
||||
try App.Channel.generateSine(&self.preview_samples, allocator, sample_rate, frequency, amplitude);
|
||||
self.preview_samples_y_range = RangeF64.init(-amplitude*1.1, amplitude*1.1);
|
||||
self.protocol_graph_cache.invalidate();
|
||||
}
|
||||
@ -850,11 +891,9 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
|
||||
|
||||
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) {
|
||||
if (self.protocol_error_message == null) {
|
||||
try App.DeviceChannel.generateSine(&device_channel.write_pattern, channel_view.sample_rate.?, frequency, amplitude);
|
||||
try App.Channel.generateSine(&channel.write_pattern, allocator, sample_rate, frequency, amplitude);
|
||||
|
||||
self.app.deferred_actions.appendAssumeCapacity(.{
|
||||
.activate = channel_view
|
||||
});
|
||||
self.app.pushCommand(.{ .start_output = channel_id });
|
||||
self.protocol_modal = null;
|
||||
}
|
||||
}
|
||||
@ -885,30 +924,22 @@ pub fn tick(self: *MainScreen) !void {
|
||||
if (ui.isKeyboardPressed(.key_escape)) {
|
||||
if (self.protocol_modal != null) {
|
||||
self.closeModal();
|
||||
} else if (self.fullscreen_channel != null) {
|
||||
self.fullscreen_channel = null;
|
||||
} else if (self.fullscreen_view != null) {
|
||||
self.fullscreen_view = null;
|
||||
} else {
|
||||
self.app.should_close = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_z)) {
|
||||
if (self.channel_undo_stack.popOrNull()) |command| {
|
||||
switch (command.action) {
|
||||
.move_and_zoom => |args| {
|
||||
const view_rect = &command.channel.view_rect;
|
||||
view_rect.x_range = args.before_x;
|
||||
view_rect.y_range = args.before_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.undoLastMoveCommand();
|
||||
}
|
||||
|
||||
const root = ui.parentBox().?;
|
||||
root.layout_direction = .top_to_bottom;
|
||||
|
||||
var maybe_modal_overlay: ?*UI.Box = null;
|
||||
if (self.protocol_modal) |channel_view| {
|
||||
if (self.protocol_modal) |channel_id| {
|
||||
const padding = UI.Padding.all(ui.rem(2));
|
||||
const modal_overlay = ui.createBox(.{
|
||||
.key = ui.keyFromString("Overlay"),
|
||||
@ -928,7 +959,7 @@ pub fn tick(self: *MainScreen) !void {
|
||||
modal_overlay.beginChildren();
|
||||
defer modal_overlay.endChildren();
|
||||
|
||||
try self.showProtocolModal(channel_view);
|
||||
try self.showProtocolModal(channel_id);
|
||||
|
||||
if (ui.signal(modal_overlay).clicked()) {
|
||||
self.closeModal();
|
||||
@ -950,26 +981,39 @@ pub fn tick(self: *MainScreen) !void {
|
||||
toolbar.beginChildren();
|
||||
defer toolbar.endChildren();
|
||||
|
||||
var start_all = ui.textButton("Start/Stop button");
|
||||
start_all.borders = UI.Borders.all(.{ .size = 4, .color = srcery.red });
|
||||
start_all.background = srcery.black;
|
||||
start_all.size.y = UI.Sizing.initFixed(.{ .parent_percent = 1 });
|
||||
start_all.padding.top = 0;
|
||||
start_all.padding.bottom = 0;
|
||||
if (ui.signal(start_all).clicked()) {
|
||||
self.app.deferred_actions.appendAssumeCapacity(.{
|
||||
.toggle_input_channels = {}
|
||||
});
|
||||
}
|
||||
if (self.app.task_read_active) {
|
||||
start_all.setText("Stop");
|
||||
{
|
||||
var btn = ui.textButton("Start/Stop button");
|
||||
btn.borders = UI.Borders.all(.{ .size = 4, .color = srcery.red });
|
||||
btn.background = srcery.black;
|
||||
btn.size.y = UI.Sizing.initFixed(.{ .parent_percent = 1 });
|
||||
btn.padding.top = 0;
|
||||
btn.padding.bottom = 0;
|
||||
if (ui.signal(btn).clicked()) {
|
||||
if (self.app.isCollectionInProgress()) {
|
||||
self.app.pushCommand(.stop_collection);
|
||||
} else {
|
||||
start_all.setText("Start");
|
||||
self.app.pushCommand(.start_collection);
|
||||
}
|
||||
}
|
||||
|
||||
if (self.fullscreen_channel) |channel| {
|
||||
try self.showChannelView(channel, UI.Sizing.initGrowFull());
|
||||
if (self.app.isCollectionInProgress()) {
|
||||
btn.setText("Stop");
|
||||
} else {
|
||||
btn.setText("Start");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var btn = ui.textButton("Save");
|
||||
btn.borders = UI.Borders.all(.{ .size = 4, .color = srcery.green });
|
||||
if (ui.signal(btn).clicked()) {
|
||||
self.app.pushCommand(.save_project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.fullscreen_view) |view_id| {
|
||||
try self.showView(view_id, UI.Sizing.initGrowFull());
|
||||
|
||||
} else {
|
||||
const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels"));
|
||||
@ -977,8 +1021,10 @@ pub fn tick(self: *MainScreen) !void {
|
||||
scroll_area.layout_direction = .top_to_bottom;
|
||||
scroll_area.layout_gap = 4;
|
||||
|
||||
for (self.app.listChannelViews()) |*channel_view| {
|
||||
try self.showChannelView(channel_view, UI.Sizing.initFixed(.{ .pixels = channel_view.height }));
|
||||
var view_iter = self.app.project.views.idIterator();
|
||||
while (view_iter.next()) |view_id| {
|
||||
const view = self.app.getView(view_id);
|
||||
try self.showView(view_id, UI.Sizing.initFixed(.{ .pixels = view.?.height }));
|
||||
}
|
||||
|
||||
{
|
||||
@ -995,24 +1041,13 @@ pub fn tick(self: *MainScreen) !void {
|
||||
const add_from_file = ui.textButton("Add from file");
|
||||
add_from_file.borders = UI.Borders.all(.{ .size = 2, .color = srcery.green });
|
||||
if (ui.signal(add_from_file).clicked()) {
|
||||
self.app.samples_mutex.unlock();
|
||||
defer self.app.samples_mutex.lock();
|
||||
|
||||
if (Platform.openFilePicker(self.app.allocator)) |filename| {
|
||||
defer self.app.allocator.free(filename);
|
||||
|
||||
// TODO: Handle error
|
||||
self.app.appendChannelFromFile(filename) catch @panic("Failed to append channel from file");
|
||||
} else |err| {
|
||||
// TODO: Show error message to user;
|
||||
log.err("Failed to pick file: {}", .{ err });
|
||||
}
|
||||
self.app.pushCommand(.add_file_from_picker);
|
||||
}
|
||||
|
||||
const add_from_device = ui.textButton("Add from device");
|
||||
add_from_device.borders = UI.Borders.all(.{ .size = 2, .color = srcery.green });
|
||||
if (ui.signal(add_from_device).clicked()) {
|
||||
self.app.current_screen = .channel_from_device;
|
||||
self.app.screen = .add_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1020,23 +1055,4 @@ pub fn tick(self: *MainScreen) !void {
|
||||
if (maybe_modal_overlay) |modal_overlay| {
|
||||
root.bringChildToTop(modal_overlay);
|
||||
}
|
||||
|
||||
if (self.app.task_read_active) {
|
||||
for (self.app.listChannelViews()) |*channel_view| {
|
||||
const device_channel = self.app.getChannelSourceDevice(channel_view) orelse continue;
|
||||
if (!channel_view.follow) continue;
|
||||
|
||||
const channel_task = device_channel.task orelse continue;
|
||||
|
||||
const sample_rate = channel_task.sample_rate;
|
||||
const sample_count: f32 = @floatFromInt(device_channel.samples.items.len);
|
||||
|
||||
channel_view.view_rect.y_range = channel_view.y_range;
|
||||
|
||||
channel_view.view_rect.x_range.lower = 0;
|
||||
if (sample_count > channel_view.view_rect.x_range.upper) {
|
||||
channel_view.view_rect.x_range.upper = sample_count + @as(f32, @floatCast(sample_rate)) * 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user