859 lines
28 KiB
Zig
859 lines
28 KiB
Zig
const std = @import("std");
|
|
const rl = @import("raylib");
|
|
const UI = @import("../ui.zig");
|
|
const App = @import("../app.zig");
|
|
const srcery = @import("../srcery.zig");
|
|
const Platform = @import("../platform/root.zig");
|
|
const RangeF64 = @import("../range.zig").RangeF64;
|
|
const Graph = @import("../graph.zig");
|
|
const Assets = @import("../assets.zig");
|
|
const utils = @import("../utils.zig");
|
|
const NIDaq = @import("../ni-daq/root.zig");
|
|
const UIView = @import("../components/view.zig");
|
|
|
|
const ViewControlsSystem = @import("../components/systems/view_controls.zig");
|
|
|
|
const MainScreen = @This();
|
|
|
|
const log = std.log.scoped(.main_screen);
|
|
const assert = std.debug.assert;
|
|
const remap = utils.remap;
|
|
const Id = App.Id;
|
|
|
|
app: *App,
|
|
view_controls: ViewControlsSystem,
|
|
modal: ?union(enum){
|
|
view_protocol: Id, // View id
|
|
notes
|
|
} = null,
|
|
|
|
// Protocol modal
|
|
frequency_input: UI.TextInputStorage,
|
|
amplitude_input: UI.TextInputStorage,
|
|
protocol_error_message: ?[]const u8 = null,
|
|
protocol_graph_cache: Graph.RenderCache = .{},
|
|
preview_sample_list_id: App.Id,
|
|
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
|
|
|
|
// Notes modal
|
|
notes_storage: UI.TextInputStorage,
|
|
|
|
// Project settings
|
|
sample_rate_input: UI.TextInputStorage,
|
|
parsed_sample_rate: ?f64 = null,
|
|
|
|
// View settings
|
|
transform_inputs: [App.View.max_transforms]UI.TextInputStorage,
|
|
channel_save_file_picker: ?Platform.FilePickerId = null,
|
|
file_save_file_picker: ?Platform.FilePickerId = null,
|
|
|
|
pub fn init(app: *App) !MainScreen {
|
|
const allocator = app.allocator;
|
|
|
|
var transform_inputs: [App.View.max_transforms]UI.TextInputStorage = undefined;
|
|
for (&transform_inputs) |*input| {
|
|
input.* = UI.TextInputStorage.init(allocator);
|
|
}
|
|
|
|
var self = MainScreen{
|
|
.app = app,
|
|
.frequency_input = UI.TextInputStorage.init(allocator),
|
|
.amplitude_input = UI.TextInputStorage.init(allocator),
|
|
.sample_rate_input = UI.TextInputStorage.init(allocator),
|
|
.notes_storage = UI.TextInputStorage.init(allocator),
|
|
.view_controls = ViewControlsSystem.init(&app.project),
|
|
.transform_inputs = transform_inputs,
|
|
.preview_sample_list_id = try app.project.addSampleList(allocator)
|
|
};
|
|
|
|
try self.frequency_input.setText("10");
|
|
try self.amplitude_input.setText("10");
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: *MainScreen) void {
|
|
self.frequency_input.deinit();
|
|
self.amplitude_input.deinit();
|
|
self.sample_rate_input.deinit();
|
|
self.notes_storage.deinit();
|
|
for (self.transform_inputs) |input| {
|
|
input.deinit();
|
|
}
|
|
self.app.project.removeSampleList(self.preview_sample_list_id);
|
|
|
|
self.clearProtocolErrorMessage();
|
|
}
|
|
|
|
pub fn showProtocolModal(self: *MainScreen, view_id: Id) !void {
|
|
var ui = &self.app.ui;
|
|
const allocator = self.app.allocator;
|
|
|
|
const view = self.app.getView(view_id) orelse return;
|
|
if (view.reference != .channel) return;
|
|
const channel_id = view.reference.channel;
|
|
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"),
|
|
.background = srcery.black,
|
|
.size_x = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
|
|
.size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
|
|
.layout_direction = .top_to_bottom,
|
|
.padding = UI.Padding.all(ui.rem(1.5)),
|
|
.flags = &.{ .clickable },
|
|
.layout_gap = ui.rem(0.5)
|
|
});
|
|
container.beginChildren();
|
|
defer container.endChildren();
|
|
|
|
{
|
|
const protocol_view = ui.createBox(.{
|
|
.key = ui.keyFromString("Protocol view"),
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initFixedPixels(ui.rem(4)),
|
|
.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 })
|
|
});
|
|
|
|
if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
|
|
const view_rect = Graph.ViewOptions{
|
|
.x_range = RangeF64.init(0, @floatFromInt(sample_list.getLength())),
|
|
.y_range = self.preview_samples_y_range
|
|
};
|
|
Graph.drawCached(&self.protocol_graph_cache, protocol_view.persistent.size, view_rect, sample_list);
|
|
if (self.protocol_graph_cache.texture) |texture| {
|
|
protocol_view.texture = texture.texture;
|
|
}
|
|
}
|
|
}
|
|
|
|
const FormInput = struct {
|
|
name: []const u8,
|
|
storage: *UI.TextInputStorage,
|
|
value: *f32
|
|
};
|
|
|
|
var frequency: f32 = 0;
|
|
var amplitude: f32 = 0;
|
|
|
|
const form_inputs = &[_]FormInput{
|
|
.{
|
|
.name = "Frequency",
|
|
.storage = &self.frequency_input,
|
|
.value = &frequency
|
|
},
|
|
.{
|
|
.name = "Amplitude",
|
|
.storage = &self.amplitude_input,
|
|
.value = &litude
|
|
},
|
|
};
|
|
var any_input_modified = false;
|
|
|
|
for (form_inputs) |form_input| {
|
|
const label = form_input.name;
|
|
const text_input_storage = form_input.storage;
|
|
|
|
const row = ui.createBox(.{
|
|
.key = ui.keyFromString(label),
|
|
.layout_direction = .left_to_right,
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initFixedPixels(ui.rem(1.5))
|
|
});
|
|
row.beginChildren();
|
|
defer row.endChildren();
|
|
|
|
const label_box = ui.label("{s}", .{ label });
|
|
label_box.size.x = UI.Sizing.initFixed(.{ .font_size = 5 });
|
|
label_box.size.y = UI.Sizing.initGrowFull();
|
|
label_box.alignment.y = .center;
|
|
|
|
try ui.textInput(.{
|
|
.key = ui.keyFromString("Text input"),
|
|
.storage = text_input_storage
|
|
});
|
|
|
|
any_input_modified = any_input_modified or text_input_storage.modified;
|
|
}
|
|
|
|
if (any_input_modified) {
|
|
self.clearProtocolErrorMessage();
|
|
|
|
for (form_inputs) |form_input| {
|
|
const label = form_input.name;
|
|
const text_input_storage: *UI.TextInputStorage = form_input.storage;
|
|
|
|
const number = std.fmt.parseFloat(f32, text_input_storage.buffer.items) catch {
|
|
try self.setProtocolErrorMessage("ERROR: {s} must be a number", .{ label });
|
|
continue;
|
|
};
|
|
|
|
if (number <= 0) {
|
|
try self.setProtocolErrorMessage("ERROR: {s} must be positive", .{ label });
|
|
continue;
|
|
}
|
|
|
|
form_input.value.* = number;
|
|
}
|
|
}
|
|
|
|
if (self.protocol_error_message == null and any_input_modified) {
|
|
if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
|
|
var preview_samples: std.ArrayListUnmanaged(f64) = .{};
|
|
defer preview_samples.deinit(allocator);
|
|
|
|
try App.Channel.generateSine(&preview_samples, allocator, sample_rate, frequency, amplitude);
|
|
sample_list.clear(allocator);
|
|
try sample_list.append(preview_samples.items);
|
|
|
|
self.preview_samples_y_range = RangeF64.init(-amplitude*1.1, amplitude*1.1);
|
|
self.protocol_graph_cache.invalidate();
|
|
}
|
|
}
|
|
|
|
if (self.protocol_error_message) |message| {
|
|
_ = ui.createBox(.{
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initFixedText(),
|
|
.text_color = srcery.red,
|
|
.align_x = .start,
|
|
.align_y = .start,
|
|
.flags = &.{ .wrap_text },
|
|
.text = message
|
|
});
|
|
}
|
|
|
|
_ = ui.createBox(.{
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initGrowFull(),
|
|
});
|
|
|
|
{
|
|
const row = ui.createBox(.{
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initFitChildren(),
|
|
.align_x = .end
|
|
});
|
|
row.beginChildren();
|
|
defer row.endChildren();
|
|
|
|
const btn = ui.textButton("Confirm");
|
|
btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
|
|
|
|
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) {
|
|
if (self.protocol_error_message == null) {
|
|
try App.Channel.generateSine(&channel.write_pattern, allocator, sample_rate, frequency, amplitude);
|
|
|
|
self.app.pushCommand(.{ .start_output = channel_id });
|
|
self.view_controls.view_protocol_modal = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
_ = ui.signal(container);
|
|
}
|
|
|
|
fn showNotesModal(self: *MainScreen) !void {
|
|
var ui = &self.app.ui;
|
|
|
|
const container = ui.createBox(.{
|
|
.key = ui.keyFromString("Notes modal"),
|
|
.background = srcery.black,
|
|
.size_x = UI.Sizing.initGrowUpTo(.{ .pixels = 400 }),
|
|
.size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 600 }),
|
|
.layout_direction = .top_to_bottom,
|
|
.padding = UI.Padding.all(ui.rem(1.5)),
|
|
.flags = &.{ .clickable },
|
|
.layout_gap = ui.rem(0.5)
|
|
});
|
|
container.beginChildren();
|
|
defer container.endChildren();
|
|
defer _ = ui.signal(container);
|
|
|
|
const label = ui.label("Notes", .{});
|
|
label.font = .{ .variant = .regular_italic, .size = ui.rem(2) };
|
|
label.alignment.x = .center;
|
|
label.size.x = UI.Sizing.initGrowFull();
|
|
|
|
_ = try ui.textInput(.{
|
|
.key = ui.keyFromString("Notes"),
|
|
.storage = &self.notes_storage,
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initGrowFull(),
|
|
.single_line = false
|
|
});
|
|
}
|
|
|
|
fn setProtocolErrorMessage(self: *MainScreen, comptime fmt: []const u8, args: anytype) !void {
|
|
self.clearProtocolErrorMessage();
|
|
|
|
const allocator = self.app.allocator;
|
|
self.protocol_error_message = try std.fmt.allocPrint(allocator, fmt, args);
|
|
}
|
|
|
|
fn clearProtocolErrorMessage(self: *MainScreen) void {
|
|
const allocator = self.app.allocator;
|
|
|
|
if (self.protocol_error_message) |msg| {
|
|
allocator.free(msg);
|
|
}
|
|
self.protocol_error_message = null;
|
|
}
|
|
|
|
fn showProjectSettings(self: *MainScreen) !void {
|
|
var ui = &self.app.ui;
|
|
const frame_allocator = ui.frameAllocator();
|
|
const project = &self.app.project;
|
|
|
|
{
|
|
const label = ui.label("Project", .{});
|
|
label.borders.bottom = .{
|
|
.color = srcery.bright_white,
|
|
.size = 1
|
|
};
|
|
}
|
|
|
|
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
|
|
|
{ // Sample rate
|
|
var placeholder: ?[]const u8 = null;
|
|
if (project.getDefaultSampleRate()) |default_sample_rate| {
|
|
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
|
|
}
|
|
|
|
_ = ui.label("Sample rate", .{});
|
|
self.parsed_sample_rate = try ui.numberInput(f64, .{
|
|
.key = ui.keyFromString("Sample rate input"),
|
|
.storage = &self.sample_rate_input,
|
|
.placeholder = placeholder,
|
|
.initial = 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) |selected_sample_rate| {
|
|
if (!allowed_sample_rates.hasInclusive(selected_sample_rate)) {
|
|
project.sample_rate = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_ = ui.checkbox(.{
|
|
.value = &project.show_rulers,
|
|
.label = "Ruler"
|
|
});
|
|
|
|
if (ui.signal(ui.textButton("Open notes")).clicked()) {
|
|
self.modal = .notes;
|
|
}
|
|
}
|
|
|
|
fn showViewSettings(self: *MainScreen, view_id: Id) !void {
|
|
var ui = &self.app.ui;
|
|
|
|
const project = &self.app.project;
|
|
const sample_rate = project.getSampleRate();
|
|
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)) });
|
|
}
|
|
|
|
_ = ui.checkbox(.{
|
|
.value = &view.sync_controls,
|
|
.label = "Sync controls"
|
|
});
|
|
|
|
|
|
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);
|
|
|
|
_ = ui.label("Channel: {s}", .{ channel_name });
|
|
|
|
if (channel_type != null) {
|
|
_ = ui.label("Type: {s}", .{ channel_type.?.name() });
|
|
} else {
|
|
_ = ui.label("Type: unknown", .{ });
|
|
}
|
|
|
|
if (ui.fileInput(.{
|
|
.key = ui.keyFromString("Save location"),
|
|
.allocator = self.app.allocator,
|
|
.file_picker = &self.channel_save_file_picker,
|
|
.path = channel.saved_collected_samples,
|
|
.open_dialog = false
|
|
})) |path| {
|
|
if (channel.saved_collected_samples) |current_path| {
|
|
self.app.allocator.free(current_path);
|
|
}
|
|
|
|
channel.saved_collected_samples = path;
|
|
}
|
|
},
|
|
.file => |file_id| {
|
|
const file = project.files.get(file_id).?;
|
|
|
|
if (ui.fileInput(.{
|
|
.key = ui.keyFromString("Filename"),
|
|
.allocator = self.app.allocator,
|
|
.file_picker = &self.file_save_file_picker,
|
|
.path = file.path
|
|
})) |path| {
|
|
self.app.allocator.free(file.path);
|
|
file.path = path;
|
|
self.app.pushCommand(.{ .reload_file = file_id });
|
|
}
|
|
}
|
|
}
|
|
|
|
const sample_list_id = project.getViewSampleListId(view_id);
|
|
const sample_list = project.sample_lists.get(sample_list_id).?;
|
|
const sample_count = sample_list.getLength();
|
|
|
|
_ = ui.label("Samples: {d}", .{ sample_count });
|
|
|
|
var duration_str: ?[]const u8 = null;
|
|
if (sample_rate != null) {
|
|
const duration = @as(f64, @floatFromInt(sample_count)) / sample_rate.?;
|
|
if (utils.formatDuration(ui.frameAllocator(), duration)) |str| {
|
|
duration_str = str;
|
|
} else |_| {}
|
|
}
|
|
if (duration_str == null) {
|
|
duration_str = std.fmt.allocPrint(ui.frameAllocator(), "{d}", .{ sample_count }) catch null;
|
|
}
|
|
|
|
_ = ui.label("Duration: {s}", .{ duration_str orelse "-" });
|
|
|
|
var deferred_remove: std.BoundedArray(usize, App.View.max_transforms) = .{};
|
|
|
|
for (0.., view.transforms.slice()) |i, *_transform| {
|
|
const transform: *App.Transform = _transform;
|
|
|
|
const row = ui.createBox(.{
|
|
.key = UI.Key.initPtr(transform),
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initFixedPixels(ui.rem(1.5)),
|
|
.layout_direction = .left_to_right
|
|
});
|
|
row.beginChildren();
|
|
defer row.endChildren();
|
|
|
|
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 });
|
|
}
|
|
}
|
|
}
|
|
|
|
fn showMarkedRange(self: *MainScreen, view_id: Id, index: usize) void {
|
|
var ui = &self.app.ui;
|
|
|
|
const view = self.app.getView(view_id) orelse return;
|
|
|
|
const marked_range = view.marked_ranges.get(index);
|
|
|
|
|
|
{
|
|
const label = ui.label("Selected range", .{});
|
|
label.borders.bottom = .{
|
|
.color = srcery.blue,
|
|
.size = 1
|
|
};
|
|
|
|
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
|
}
|
|
|
|
const sample_rate = self.app.project.getSampleRate();
|
|
if (marked_range.axis == .X and sample_rate != null) {
|
|
_ = ui.label("From: {d:.3}s", .{ marked_range.range.lower / sample_rate.? });
|
|
_ = ui.label("To: {d:.3}s", .{ marked_range.range.upper / sample_rate.? });
|
|
_ = ui.label("Size: {d:.3}s", .{ marked_range.range.size() / sample_rate.? });
|
|
} else {
|
|
_ = ui.label("From: {d:.2}", .{ marked_range.range.lower });
|
|
_ = ui.label("To: {d:.2}", .{ marked_range.range.upper });
|
|
_ = ui.label("Size: {d:.2}", .{ marked_range.range.size() });
|
|
}
|
|
|
|
_ = ui.label("Samples: {d:.2}", .{ marked_range.range.size() });
|
|
|
|
if (marked_range.axis == .X) {
|
|
if (marked_range.min) |min| {
|
|
_ = ui.label("Minimum: {d:.3}", .{ min });
|
|
} else{
|
|
_ = ui.label("Minimum: <unknown>", .{});
|
|
}
|
|
|
|
if (marked_range.max) |max| {
|
|
_ = ui.label("Maximum: {d:.3}", .{ max });
|
|
} else{
|
|
_ = ui.label("Maximum: <unknown>", .{});
|
|
}
|
|
|
|
if (marked_range.average) |average| {
|
|
_ = ui.label("Average: {d:.3}", .{ average });
|
|
} else{
|
|
_ = ui.label("Average: <unknown>", .{});
|
|
}
|
|
|
|
if (marked_range.standard_deviation) |standard_deviation| {
|
|
_ = ui.label("Standard deviation: {d:.3}", .{ standard_deviation });
|
|
} else{
|
|
_ = ui.label("Standard deviation: <unknown>", .{});
|
|
}
|
|
}
|
|
|
|
_ = ui.createBox(.{ .size_y = UI.Sizing.initGrowFull() });
|
|
|
|
{
|
|
const btn = ui.textButton("Remove");
|
|
btn.borders = UI.Borders.all(.{ .color = srcery.red, .size = 4 });
|
|
const signal = ui.signal(btn);
|
|
if (signal.clicked() or ui.isKeyboardPressed(.key_backspace)) {
|
|
self.view_controls.show_marked_range = null;
|
|
_ = view.marked_ranges.swapRemove(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn showToolbar(self: *MainScreen) void {
|
|
var ui = &self.app.ui;
|
|
|
|
const toolbar = ui.createBox(.{
|
|
.background = srcery.black,
|
|
.layout_direction = .left_to_right,
|
|
.size_x = .{ .fixed = .{ .parent_percent = 1 } },
|
|
.size_y = .{ .fixed = .{ .font_size = 2 } },
|
|
.borders = .{
|
|
.bottom = .{ .color = srcery.hard_black, .size = 4 }
|
|
}
|
|
});
|
|
toolbar.beginChildren();
|
|
defer toolbar.endChildren();
|
|
|
|
{
|
|
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 {
|
|
self.app.pushCommand(.start_collection);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
_ = ui.createBox(.{ .size_x = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
|
|
|
{
|
|
var btn = ui.textButton("Move");
|
|
if (self.view_controls.selected_tool == .move) {
|
|
btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
|
}
|
|
|
|
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_one)) {
|
|
self.view_controls.selected_tool = .move;
|
|
}
|
|
}
|
|
|
|
{
|
|
var btn = ui.textButton("Select");
|
|
if (self.view_controls.selected_tool == .select) {
|
|
btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
|
|
}
|
|
|
|
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_two)) {
|
|
self.view_controls.selected_tool = .select;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn showSidePanel(self: *MainScreen) !void {
|
|
var ui = &self.app.ui;
|
|
|
|
const container = ui.createBox(.{
|
|
.size_x = UI.Sizing.initFitChildren(),
|
|
.size_y = UI.Sizing.initGrowFull(),
|
|
.borders = .{
|
|
.right = .{ .color = srcery.hard_black, .size = 4 }
|
|
},
|
|
.layout_direction = .top_to_bottom,
|
|
.padding = UI.Padding.all(ui.rem(1)),
|
|
.layout_gap = ui.rem(0.2)
|
|
});
|
|
container.beginChildren();
|
|
defer container.endChildren();
|
|
|
|
_ = ui.createBox(.{ .size_x = UI.Sizing.initFixedPixels(ui.rem(12)) });
|
|
|
|
if (self.view_controls.show_marked_range) |show_marked_range| {
|
|
self.showMarkedRange(show_marked_range.view_id, show_marked_range.index);
|
|
} else if (self.view_controls.view_settings) |view_id| {
|
|
try self.showViewSettings(view_id);
|
|
} else {
|
|
try self.showProjectSettings();
|
|
}
|
|
}
|
|
|
|
pub fn tick(self: *MainScreen) !void {
|
|
var ui = &self.app.ui;
|
|
|
|
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_z)) {
|
|
self.view_controls.undoLastMove();
|
|
}
|
|
|
|
const root = ui.parentBox().?;
|
|
root.layout_direction = .top_to_bottom;
|
|
|
|
if (self.view_controls.view_protocol_modal) |view_protocol_modal| {
|
|
if (self.modal == null) {
|
|
self.modal = .{ .view_protocol = view_protocol_modal };
|
|
}
|
|
self.view_controls.view_protocol_modal = null;
|
|
}
|
|
|
|
const was_modal_open = self.modal != null;
|
|
|
|
var maybe_modal_overlay: ?*UI.Box = null;
|
|
if (self.modal != null) {
|
|
const padding = UI.Padding.all(ui.rem(2));
|
|
const modal_overlay = ui.createBox(.{
|
|
.key = ui.keyFromString("Overlay"),
|
|
.float_rect = .{
|
|
.x = 0,
|
|
.y = 0,
|
|
// TODO: This is a hack, UI core should handle this
|
|
.width = root.persistent.size.x - padding.byAxis(.X),
|
|
.height = root.persistent.size.y - padding.byAxis(.Y)
|
|
},
|
|
.background = rl.Color.black.alpha(0.6),
|
|
.flags = &.{ .clickable, .scrollable },
|
|
.padding = padding,
|
|
.align_x = .center,
|
|
.align_y = .center,
|
|
});
|
|
modal_overlay.beginChildren();
|
|
defer modal_overlay.endChildren();
|
|
|
|
switch (self.modal.?) {
|
|
.view_protocol => |view_id| try self.showProtocolModal(view_id),
|
|
.notes => try self.showNotesModal()
|
|
}
|
|
|
|
if (ui.signal(modal_overlay).clicked()) {
|
|
self.modal = null;
|
|
}
|
|
|
|
maybe_modal_overlay = modal_overlay;
|
|
}
|
|
|
|
self.showToolbar();
|
|
|
|
const ui_view_ctx = UIView.Context{
|
|
.app = self.app,
|
|
.ui = &self.app.ui,
|
|
.view_controls = &self.view_controls
|
|
};
|
|
|
|
if (self.view_controls.view_fullscreen) |view_id| {
|
|
_ = try UIView.show(ui_view_ctx, view_id, UI.Sizing.initGrowFull());
|
|
|
|
} else {
|
|
const container = ui.createBox(.{
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initGrowFull(),
|
|
.layout_direction = .left_to_right
|
|
});
|
|
container.beginChildren();
|
|
defer container.endChildren();
|
|
|
|
try self.showSidePanel();
|
|
|
|
const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels"));
|
|
defer ui.endScrollbar();
|
|
scroll_area.layout_direction = .top_to_bottom;
|
|
|
|
var view_iter = self.app.project.views.idIterator();
|
|
while (view_iter.next()) |view_id| {
|
|
const view = self.app.getView(view_id);
|
|
_ = try UIView.show(ui_view_ctx, view_id, UI.Sizing.initFixed(.{ .pixels = view.?.height }));
|
|
}
|
|
|
|
{
|
|
const add_channel_view = ui.createBox(.{
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initFixed(.{ .pixels = 200 }),
|
|
.align_x = .center,
|
|
.align_y = .center,
|
|
.layout_gap = 32
|
|
});
|
|
add_channel_view.beginChildren();
|
|
defer add_channel_view.endChildren();
|
|
|
|
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.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.screen = .add_channels;
|
|
}
|
|
}
|
|
}
|
|
|
|
self.view_controls.applyCommands();
|
|
|
|
if (maybe_modal_overlay) |modal_overlay| {
|
|
root.bringChildToTop(modal_overlay);
|
|
}
|
|
|
|
if (ui.isKeyboardPressed(.key_escape)) {
|
|
if (self.modal != null) {
|
|
self.modal = null;
|
|
} else if (self.view_controls.view_fullscreen != null) {
|
|
self.view_controls.view_fullscreen = null;
|
|
} else if (self.view_controls.view_settings != null) {
|
|
self.view_controls.view_settings = null;
|
|
} else if (self.view_controls.show_marked_range != null) {
|
|
self.view_controls.show_marked_range = null;
|
|
} else {
|
|
self.app.should_close = true;
|
|
}
|
|
}
|
|
|
|
const is_modal_open = self.modal != null;
|
|
if (!was_modal_open and is_modal_open) {
|
|
self.protocol_graph_cache.clear();
|
|
}
|
|
} |